Declarative, human-in-the-loop workflow runner for VS Code chat.
Write branching YAML playbooks that orchestrate your language model and MCP tools โ with review and input steps โ and run them from the @skiller chat participant.
Skiller runs skills โ declarative, multi-step workflows defined in YAML and Markdown. Each skill is a playbook: a sequence of steps that call a language model, invoke your MCP tools, and pause for your input or approval exactly where you tell them to.
It is deliberately not a free-form agent and not an "Agent Skills" auto-loader:
| Free-form agent | Skiller | |
|---|---|---|
| Control flow | Model decides what to do next | You define the steps; the model fills them in |
| Side effects | Can act on its own | Runs only the steps you wrote, pausing for confirmation where you ask |
| Reproducibility | Varies run to run | Same playbook, same shape every time |
| Branching | Implicit | Explicit confirmation steps with goto jumps |
If you want predictable, reviewable automation that still leverages an LLM โ not an autonomous agent โ Skiller is for you.
- ๐งฉ Declarative skills โ author workflows as plain YAML + Liquid-templated Markdown, no code.
- โ Human-in-the-loop โ
confirmationsteps pause for your choice and can branch or jump (goto_step). - ๐ง MCP tool orchestration โ call your configured MCP tools from
llmandtoolsteps. - ๐บ๏ธ Live execution graph โ watch a skill run as a Mermaid state diagram in a side panel, with branches and
gotoloops lighting up as they fire. - ๐ Layered discovery โ workspace, user, and built-in skills, with workspace winning.
- ๐งช Typed & validated โ manifests are schema-checked (Zod) with helpful errors before they run.
- VS Code 1.93+
- A chat language model provider (e.g. GitHub Copilot, or any provider exposing VS Code's Language Model API)
- MCP servers configured in VS Code (optional โ only needed for tool integrations)
From the Marketplace (once published):
ext install tivaliy.skiller
From source:
npm install
npm run package
code --install-extension skiller-*.vsixOpen the Chat view and talk to @skiller:
@skiller /help Show available commands
@skiller /skills List available skills
@skiller /skill greeter Run the bundled "greeter" skill
A message that is not a slash command and not a skill gets a short hint โ Skiller does not do free-form chat.
| Command | Description |
|---|---|
/help |
Show available commands |
/skills |
List discovered skills |
/skill <id> |
Run a skill |
/tools |
Show available MCP tools |
/models |
Show language models available for skill configuration |
/reload |
Reload tools and skills |
A skill is a directory containing a skill.yaml manifest and one or more Liquid-templated step files. Skills are discovered from three sources, in precedence order (earlier wins):
- Workspace โ
.skiller/skills/in your repo root - User โ
~/.vscode/skiller/skills/ - Built-in โ the
skills/bundled with the extension
A skill with the same id in your workspace overrides the user copy, which overrides the built-in one โ so you can fork and customize any bundled skill.
| Type | What it does | Key fields |
|---|---|---|
llm |
Calls the model with a Liquid-templated prompt (optionally using MCP tools), stores the reply | file, model, output |
confirmation |
Pauses and shows a message with choices; can continue, abort, or jump | message, options[].action (continue / abort / goto + goto_step) |
tool |
Invokes one MCP tool directly with templated params | tool (alias), params |
A skill is a folder with a skill.yaml manifest plus one Markdown prompt per llm step. The bundled mind-reader game is a good tour of the moving parts โ run it with @skiller /skill mind-reader, then read how it's built below. Your own skills go under .skiller/skills/<id>/:
# .skiller/skills/mind-reader/skill.yaml (abridged โ full source in the repo)
id: mind-reader
name: Mind Reader
description: Twenty Questions โ think of anything and the model guesses it.
version: "1.0.0"
# Collected once before the skill runs. enum gives a hint; default lets the player just hit enter.
inputs:
- name: category
type: string
required: false
default: "anything"
enum: ["anything", "an animal", "an object", "a famous person", "a place"]
prompt: "Think of something and keep it secret. What kind of thing is it?"
models:
default: gpt-4o
steps:
# 1. Ask the next yes/no question. Steps share no memory, so the model carries
# what it knows forward in outputs.turn.notes (see the prompt file below).
- id: ask
type: llm
file: steps/01-ask.md
output: turn
# 2. The player clicks an answer. Yes/No/Unsure loop BACK to `ask`; "guess now"
# branches to `guess`. The clicked label lands in outputs.reply.selectedOption.
- id: answer
type: confirmation
message: "{{ outputs.turn.question }}"
options:
- { label: "Yes", action: goto, goto_step: ask }
- { label: "No", action: goto, goto_step: ask }
- { label: "Sometimes / Not sure", action: goto, goto_step: ask }
- { label: "I'm ready โ guess now!", action: goto, goto_step: guess }
output: reply
# 3. Commit to a guess from the notes.
- id: guess
type: llm
file: steps/02-guess.md
output: final
# 4. Right? "Nope" loops back to keep asking; "Stop" ends it.
- id: verdict
type: confirmation
message: "My guess: **{{ outputs.final.guess }}** โ did I get it?"
options:
- { label: "Yes! ๐", action: continue }
- { label: "Nope โ keep asking", action: goto, goto_step: ask }
- { label: "Stop here", action: abort }
output: result
on_error: abort
output:
summary: "๐ฎ Run `/skill mind-reader` again to play more."The goto options are what make it a game: three of answer's buttons jump back to ask (a loop), one jumps forward to guess (a branch) โ that looping/branching is exactly what the live graph animates. Because steps don't share conversation history, the model threads its progress through outputs โ a running notes blob it rewrites each turn.
Each llm step runs a Markdown + Liquid prompt with access to {{ inputs.* }} and {{ outputs.* }}. Here's steps/01-ask.md (abridged):
---
id: ask
---
You're playing Twenty Questions; the player is thinking of {{ inputs.category }}.
{% if outputs.turn %}
What you know so far:
{{ outputs.turn.notes }}
The player's answer to "{{ outputs.turn.question }}" was: {{ outputs.reply.selectedOption }}
{% else %}
This is your first question โ start broad.
{% endif %}
Reply with ONLY JSON: { "notes": "...updated facts...", "question": "your next yes/no question" }{% if outputs.turn %} guards the first turn, when no prior output exists yet โ Liquid's lenientIf treats the undefined value as falsy instead of erroring.
Tool steps write files. mind-reader stays in chat, but to produce a file add a tool step backed by a built-in tool:
tools:
aliases:
create_file: skiller_createFile
steps:
- id: save
type: tool
tool: create_file
params:
filePath: "notes.md"
content: "{{ outputs.turn.notes }}"Two working skills ship with the extension:
greeterโ a tinyllm+confirmationflow with anabortbranch. The "hello world" of skills.mind-readerโ a game of Twenty Questions: think of anything and the model guesses it through yes/no questions. Itsgotoloops and branches make the live execution graph come alive, and it stays entirely in chat (writes nothing to your workspace).
Run either with @skiller /skill <id>. greeter is the simplest starting point; the mind-reader walkthrough above shows the full surface โ typed input, looping goto branches, and human-in-the-loop review.
Skiller registers two language-model tools that work reliably in skill tool steps and in chat:
skiller_createFileโ create a file at a path with given content.skiller_replaceInFileโ replace an exact substring within an existing file.
| Setting | Default | Description |
|---|---|---|
skiller.skills.verboseMode |
"off" |
Debug output: "off", "rendered", or "raw" |
skiller.skills.toolInvocationTimeout |
60000 |
MCP tool timeout (ms) |
skiller.skills.maxToolIterations |
10 |
Max tool calls per skill step |
skiller.llm.maxHistoryTurns |
20 |
Conversation turns sent to the LLM |
skiller.llm.maxToolResponseLength |
4000 |
Tool response truncation limit (chars) |
skiller.llm.maxToolResponses |
10 |
Tool responses kept in follow-up context |
npm install
npm run compile # dev build (esbuild)
npm run watch # rebuild on change
npm run typecheck # tsc --noEmit (src)
npm run typecheck:test # tsc --noEmit (src + tests)
npm run test # unit tests (vitest)
npm run package # build a .vsixPress F5 in VS Code to launch an Extension Development Host with Skiller loaded.
Issues and pull requests are welcome. Please:
- Run
npm run typecheck:testandnpm run testbefore opening a PR. - Keep changes focused and add tests for new behavior.
- Follow the existing code style and patterns.
Licensed under the MIT License.