skill.yaml reference
The skill.yaml manifest is validated against a strict schema. Unknown keys are rejected (with
did-you-mean suggestions, so typos surface immediately), name and at least one step are
required, and step ids and output names must be unique. Keys are snake_case — on_error,
tool_mode, goto_step.
This page documents every field. For the YAML that goes at the top of a step’s prompt file, see
Step-file frontmatter. For the {{ ... }} syntax used in values, see
Templating with Liquid.
Top-level keys
Section titled “Top-level keys”| Key | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Human-readable name |
steps | list | yes | At least one step; see Steps |
description | string | no | Default "" |
version | string | no | Semver (x.y.z), default 1.0.0 |
id | string | no | Defaults to the folder name |
author | string | no | — |
inputs | list | no | See Inputs |
models | map | no | default + aliases; see Models |
tools | map | no | aliases; see Tools |
on_error | enum | no | abort (default) or continue |
output | map | no | summary, and an optional to output sink |
The greeter example skill uses most of them:
id: greetername: Greeterdescription: A simple greeting workflow demonstrating multi-step skills with confirmationversion: "1.1.0"
inputs: - name: name type: string description: Your name required: true prompt: "What is your name?"
models: default: gpt-4o
steps: - id: greet type: llm file: steps/01-greet.md output: greeting
- id: confirm type: confirmation message: | {{ outputs.greeting }}
Would you like me to also generate a fun fact? options: - { label: "Yes, give me a fun fact", action: continue } - { label: "No thanks, just the greeting", action: abort } output: user_choice
- id: fact type: llm file: steps/02-fact.md model: gpt-4.1 # per-step model override output: fun_fact
on_error: abort
output: summary: "{{ outputs.fun_fact }}"Inputs
Section titled “Inputs”inputs[] declares the values collected from the user before the skill runs. Each input is read in
templates as {{ inputs.<name> }}.
| Field | Type | Notes |
|---|---|---|
name | string | Required. Unique within the skill |
type | enum | string (default), number, boolean, array |
description | string | Default "" |
required | boolean | Default true |
default | any | Used when the value is not provided |
prompt | string | Shown when the value is collected interactively |
enum | list of strings | Restrict to these values (≥1 entry) |
pattern | string | Regex the value must match (string inputs) |
from | string | Fill this input from editor context at launch instead of prompting; see Binding inputs to editor context |
inputs: - name: category type: string required: false default: "anything" prompt: "What kind of thing is it?" enum: # offer a fixed set of choices - "anything" - "an animal" - "a place"
- name: ticket type: string required: true prompt: "Ticket key?" pattern: "^[A-Z]+-[0-9]+$" # e.g. PROJ-123Pass inputs when launching: @skiller /skill <id> ticket=PROJ-12 "positional value" — named values
match by name; positional values fill the remaining inputs in declaration order.
Binding inputs to editor context
Section titled “Binding inputs to editor context”from fills an input from the editor at launch instead of prompting — so a skill launched from the
editor (a code action, the context menu, the Command Palette) picks up the selection, file, diff, or
diagnostics it needs without asking. An explicit launch argument or a default always wins; the
context only fills an input that would otherwise be empty.
from value | Resolves to |
|---|---|
selection | The selected text in the active editor |
activeFile | The active file’s full text (alias for activeFile.content) |
activeFile.content | The active file’s full text |
activeFile.path | The active file’s path |
activeFile.language | The active file’s language id (e.g. typescript) |
git.staged | git diff --staged for the workspace |
git.working | git diff (unstaged working-tree changes) |
diagnostics | The active file’s problems, one ‹line›: ‹message› per line |
inputs: - name: code type: string from: selection # filled from the highlighted code; prompts if nothing is selected - name: language type: string from: activeFile.languagefrom is validated against the list above, so a typo is caught when the skill is parsed rather than
silently resolving to nothing. For the full launch-and-deliver model see
Editor-native skills.
Models
Section titled “Models”models is optional. Omit it to use the model selected in the chat picker.
| Field | Type | Notes |
|---|---|---|
default | string | Model used by steps that don’t set their own model |
aliases | map | Friendly name → model ID, usable as a step’s model |
models: default: gpt-4o aliases: fast: gpt-4o-mini smart: gpt-4oA step then refers to an alias or a direct ID via its model field (see llm steps).
A model picked in the chat model dropdown overrides all of this and shows a banner — see
Use & override models.
tools.aliases maps a friendly name to an underlying tool name — a built-in tool
or a configured MCP tool. Steps usually reference the alias (a tool step also accepts a raw tool name, but aliasing is recommended). Append ? to mark a
tool optional: if it isn’t available at run time, steps that use it are skipped instead of failing.
tools: aliases: create_file: skiller_createFile # alias -> built-in LM tool search: my_mcp_searchTool # alias -> a configured MCP tool notify: my_mcp_notify? # optional: skipped if unavailablesteps[] is the ordered list of work. Every step shares a set of common fields; each type then adds
its own.
Common fields (all step types):
| Field | Required | Notes |
|---|---|---|
id | yes | Unique. Starts with a letter; letters, numbers, _, - |
type | yes | llm, confirmation, or tool — no default |
description | no | Shown in the live execution graph |
output | no | Unique name; stores the step result as outputs.<name> |
when | no | Liquid condition; the step is skipped when it evaluates falsy |
requires | no | List of step ids that must run before this one |
when is evaluated permissively — an undefined variable is treated as falsy rather than throwing,
so it is safe to guard a step against state that may not exist yet:
- id: summarize type: llm file: steps/summarize.md when: "outputs.findings" # bare expression — runs only once findings exist requires: [research] # research must run first output: summaryWrite when as a bare Liquid expression (e.g. outputs.findings, inputs.count > 0) — Skiller
evaluates its truthiness for you. Don’t wrap it in {% if %} or {{ }}; a wrapped value never
evaluates correctly and the step is always skipped.
llm steps
Section titled “llm steps”Send a prompt to the model. Provide the prompt with exactly one of file or message.
| Field | Notes |
|---|---|
file | Path to a Markdown prompt file, relative to the skill folder |
message | Inline prompt (alternative to file) |
model | Alias or direct model ID; overrides models.default for this step |
tools | List of tool aliases the model may call (agentic use) |
tool_mode | auto (model decides) or required (must call a tool; needs non-empty tools) |
tools: aliases: search: my_mcp_searchTool # a real MCP tool you configured in VS Codesteps: - id: research type: llm file: steps/01-research.md tools: [search] tool_mode: auto # "auto" (model decides) | "required" (must call a tool; needs non-empty tools) output: findingsWhen an llm reply is valid JSON it is auto-parsed, so you can read fields with
{{ outputs.findings.title }}. Otherwise the reply is stored as a raw string.
confirmation steps
Section titled “confirmation steps”Pause and show the user a choice. Provide the message with message or file.
| Field | Notes |
|---|---|
message | Inline prompt text (Liquid-interpolated) |
file | Path to a Markdown message file (alternative to message) |
options | List of choices; defaults to Continue / Cancel if omitted |
Each entry in options:
| Field | Required | Notes |
|---|---|---|
label | yes | Button text |
action | yes | continue, abort, or goto |
goto_step | when action: goto | Target step id to jump to |
A goto can point backward (loop) or forward (branch):
- id: answer type: confirmation message: "{{ outputs.turn.question }}" options: - { label: "Yes", action: goto, goto_step: ask } # loop back - { label: "No", action: goto, goto_step: ask } - { label: "I'm ready — guess now!", action: goto, goto_step: guess } # branch forward output: replyThe step’s output is an object:
| Field | Type | Notes |
|---|---|---|
selectedOption | string | The chosen option’s label |
selectedIndex | number | 1-based index as shown to the user |
action | string | continue, abort, or goto |
timestamp | number | When the user confirmed |
Read it as {{ outputs.reply.selectedOption }}. See
Branch & loop with confirmations for the full pattern.
tool steps
Section titled “tool steps”Invoke a single tool. Takes a tool (an alias from tools.aliases, or a raw tool name) and optional params. A tool
step must not carry tools or tool_mode — those apply only to llm steps.
| Field | Required | Notes |
|---|---|---|
tool | yes | A tool alias from tools.aliases, or a raw tool name |
params | no | Map passed to the tool; values are recursively Liquid-interpolated |
tools: aliases: create_file: skiller_createFile # alias -> built-in LM toolsteps: - id: save type: tool tool: create_file params: filePath: "notes.md" content: "{{ outputs.draft.text }}" output: savedon_error
Section titled “on_error”Top-level. Controls what happens when a step fails.
| Value | Behavior |
|---|---|
abort | Stop the skill on the first error (default) |
continue | Skip the failed step and keep going |
output
Section titled “output”Top-level, optional. Two keys:
| Key | Type | Notes |
|---|---|---|
summary | string | Liquid template rendered after the skill finishes — the closing message |
to | string | Optional output sink: where summary is delivered. Omit for chat only |
output.summary is rendered and shown to the user when the skill completes:
output: summary: "✅ Saved to {{ outputs.saved.filePath }}."Output sinks (output.to)
Section titled “Output sinks (output.to)”When to is set, the rendered summary is delivered to that destination instead of (just) being
shown in chat. The destination is captured at launch, so an editor sink writes back where the skill
was started even after chat takes focus.
to value | Delivers to |
|---|---|
newDocument | A new untitled document |
file:‹path› | A workspace file at ‹path› (created/overwritten; workspace-confined like the file tools) |
editor.replaceSelection | Replaces the launch selection (inserts at the caret if there was none) |
editor.insert | Inserts at the launch caret |
diff | Opens a reviewable diff against the launch document — you Apply it |
terminal | Types the text into the terminal without running it — you review and press Enter |
terminal.run | Types the text into the terminal and runs it — pair with a confirmation step, since it executes on delivery |
output: summary: "{{ outputs.command }}" to: terminal.run # the shell-it skill: drafts a command, then runs it after you confirmto may be templated (file:notes/{{ inputs.name }}.md). The write-back sinks
(editor.replaceSelection, editor.insert, diff) never clobber: if the launch document changed
since the skill started, the result opens in a new tab instead. For the full model and safety
behaviour see Editor-native skills.
See also
Section titled “See also”- Step-file frontmatter — the YAML at the top of a step’s prompt file.
- Templating with Liquid — the
{{ ... }}and{% ... %}syntax. - Step types & state — how
llm,confirmation, andtoolsteps run. - Editor-native skills —
from:context inputs andoutput.tosinks in full.