# How Skills Work in Kilo Code A deep dive into the skills system in Kilo Code, explaining discovery, loading, activation, and best practices for writing skills that trigger reliably. ## Table of Contents - [Discovery vs Loading vs Activation](#discovery-vs-loading-vs-activation) - [What Goes in the System Prompt](#what-goes-in-the-system-prompt) - [How Matching Works](#how-matching-works) - [Mode-Specific Skills](#mode-specific-skills) - [Override Priority](#override-priority) - [UI Indicators](#ui-indicators) - [Best Practices for Reliable Triggering](#best-practices-for-reliable-triggering) - [Code References](#code-references) --- ## Discovery vs Loading vs Activation ### Discovery (Startup) Happens when Kilo Code starts, in `SkillsManager.discoverSkills()`: - Scans `.kilocode/skills/` (project-level) and `~/.kilocode/skills/` (global) - Also scans mode-specific directories like `skills-code/`, `skills-architect/` - Parses SKILL.md frontmatter (**only name + description**) - Stores metadata in memory (`Map`) ```typescript // From src/services/skills/SkillsManager.ts async discoverSkills(): Promise { this.skills.clear() const skillsDirs = await this.getSkillsDirectories() for (const { dir, source, mode } of skillsDirs) { await this.scanSkillsDirectory(dir, source, mode) } } ``` ### Loading (On-Demand) Reading the full SKILL.md content happens **only when needed** via `getSkillContent()`: ```typescript // From src/services/skills/SkillsManager.ts async getSkillContent(name: string, currentMode?: string): Promise { // ... find skill ... const fileContent = await fs.readFile(skill.path, "utf-8") const { content: body } = matter(fileContent) return { ...skill, instructions: body.trim(), } } ``` ### Activation When the LLM decides to use a skill. The SKILL.md body is read at that moment via `read_file` tool. --- ## What Goes in the System Prompt **Only name, description, and path** - NOT the full SKILL.md content. From `src/core/prompts/sections/skills.ts`: ```xml translation Guidelines for translating and localizing... /absolute/path/to/SKILL.md REQUIRED PRECONDITION Before producing ANY user-facing response, you MUST perform a skill applicability check. Step 1: Skill Evaluation - Evaluate the user's request against ALL available skill entries - Determine whether at least one skill clearly and unambiguously applies Step 2: Branching Decision - Select EXACTLY ONE skill - Prefer the most specific skill when multiple skills match - Read the full SKILL.md file at the skill's - Load the SKILL.md contents fully into context BEFORE continuing - Follow the SKILL.md instructions precisely - Proceed with a normal response - Do NOT load any SKILL.md files ``` --- ## How Matching Works **The LLM decides** - there's no keyword matching, semantic search, or code-based logic. The system prompt instructs the LLM to: 1. Evaluate your request against ALL skill descriptions 2. If a skill "clearly and unambiguously applies" → read the SKILL.md file 3. If no skill applies → proceed normally ### Example - Skill: `overnight-run` with description "autonomous overnight workflow execution" - Your message: "run this overnight autonomously" - The LLM sees the skill list, evaluates descriptions, and decides whether to `read_file` the SKILL.md ### When is it checked? **Every response** - the `mandatory_skill_check` is part of the system prompt sent with every request. --- ## Mode-Specific Skills Skills in `skills-code/` are **filtered** to only appear when in code mode: ```typescript // From src/services/skills/SkillsManager.ts getSkillsForMode(currentMode: string): SkillMetadata[] { const resolvedSkills = new Map() for (const skill of this.skills.values()) { // Skip mode-specific skills that don't match current mode if (skill.mode && skill.mode !== currentMode) continue // ... override resolution ... } return Array.from(resolvedSkills.values()) } ``` So `skills-code/overnight-run/` would only show up in the system prompt when you're in code mode - it's not just hidden in UI, it's **not sent to the LLM at all** in other modes. --- ## Override Priority If you have the same skill name in multiple places: 1. **Project > Global** - project-level skills override global ones 2. **Mode-specific > Generic** - `skills-code/foo/` overrides `skills/foo/` when in code mode ```typescript // From src/services/skills/SkillsManager.ts private shouldOverrideSkill(existing: SkillMetadata, newSkill: SkillMetadata): boolean { // Project always overrides global if (newSkill.source === "project" && existing.source === "global") return true if (newSkill.source === "global" && existing.source === "project") return false // Same source: mode-specific overrides generic if (newSkill.mode && !existing.mode) return true if (!newSkill.mode && existing.mode) return false return false } ``` --- ## UI Indicators ### What Exists The **Settings → Skills tab** shows: - List of discovered skills (project + global) - Their descriptions and mode restrictions - Delete buttons This is inventory management only - it doesn't track which skills were actually used. ### What Doesn't Exist **No dedicated "skill activated" indicator.** There's no badge, toast, or status bar showing "Skill X activated". ### How to Know a Skill Was Used 1. **`read_file` tool call** - The most visible sign. When the LLM uses a skill, it calls `read_file` on the SKILL.md path. You'll see this in the chat. 2. **LLM's response text** - The LLM might mention "I'm using the translation skill" but this is model-dependent. 3. **Internal verification** - The prompt includes `true|false` for the LLM's internal reasoning, but this isn't parsed or displayed. --- ## Best Practices for Reliable Triggering ### 1. Description is Everything The description is what the LLM evaluates. Make it match how users phrase requests. **Good:** "autonomous overnight workflow execution with progress monitoring" **Bad:** "overnight stuff" ### 2. Be Specific Vague descriptions lead to uncertain matching. ### 3. Use Action Words Descriptions should describe WHEN to use the skill: - "Guidelines for translating and localizing..." - "Workflow for deploying to production..." - "Process for reviewing pull requests..." ### 4. Explicit Invocation Always Works Saying "use the overnight-run skill" will definitely trigger it since the LLM sees the skill name in the list. ### 5. Test with Variations The LLM interprets, so "run overnight" might work but "schedule for tonight" might not. Test your skill with different phrasings. ### 6. Keep Names Lowercase with Hyphens Per the spec: 1-64 chars, lowercase letters/numbers/hyphens only, no leading/trailing hyphens, no consecutive hyphens. --- ## Code References | File | Purpose | |------|---------| | `src/services/skills/SkillsManager.ts` | Discovery, loading, mode filtering | | `src/core/prompts/sections/skills.ts` | System prompt generation | | `src/shared/skills.ts` | Type definitions | | `webview-ui/src/components/kilocode/settings/InstalledSkillsView.tsx` | UI for viewing installed skills | --- ## SKILL.md Structure ```markdown --- name: my-skill description: When to use this skill - this is what the LLM evaluates --- # Instructions The full content here is only loaded when the skill is activated. This can be as long as needed - it's not in the system prompt by default. ``` --- *Based on Kilo Code source code analysis, January 2026*