Skip to content

v4: Skills Mechanism

Knowledge Externalization: Paradigm Shift from Training to Editing

The Skills mechanism embodies a profound paradigm shift: Knowledge Externalization.

Traditional Approach: Knowledge Internalized in Parameters

Traditional AI systems have all their knowledge locked in model parameters. You can’t access, modify, or reuse it.

Want to teach the model new skills? You need to:

  1. Collect large amounts of training data
  2. Set up distributed training clusters
  3. Perform complex parameter fine-tuning (LoRA, full fine-tuning, etc.)
  4. Deploy new model versions

It’s like the brain suddenly amnesiac, but you have no notes to recover memory. Knowledge is locked in the neural network’s weight matrices, completely opaque to users.

New Paradigm: Knowledge Externalized to Documents

Code execution paradigm changes everything.

┌─────────────────────────────────────────────────────────────────┐
│                     Knowledge Storage Layer                      │
│                                                                  │
│  Model Parameters → Context Window → File System → Skill Library │
│       (Internal)          (Runtime)       (Persistent)      (Structured)   │
│                                                                  │
│  ←───────── Training Modifies ─────────→  ←──── Natural Language Modifies ──→  │
│     Requires cluster, data, expertise              Anyone can edit              │
└─────────────────────────────────────────────────────────────────┘

Key breakthrough:

  • Past: Modify model behavior = modify parameters = need training = need GPU cluster + training data + expertise
  • Present: Modify model behavior = modify SKILL.md = edit text file = anyone can do it

This is like adding a hot-pluggable LoRA weight to the base model, but you don’t need any parameter training on the model itself.

Why This Matters

  1. Democratization: No longer need ML expertise to customize model behavior
  2. Transparency: Knowledge stored in human-readable Markdown, auditable and understandable
  3. Reusability: Write a Skill once, use on any compatible Agent
  4. Version control: Git manages knowledge changes, supports collaboration and rollback
  5. Online learning: Model “learns” in larger context windows without offline training

Traditional fine-tuning is offline learning: collect data → train → deploy → use. Skills are online learning: load knowledge on-demand at runtime, immediate effect.

Knowledge Layer Comparison

LayerModification生效时间PersistenceCost
Model ParametersTraining/fine-tuningHours-daysPermanent$10K-$1M+
Context WindowAPI callInstantWithin session~$0.01/call
File SystemEdit fileNext loadPermanentFree
Skill LibraryEdit SKILL.mdNext triggerPermanentFree

Skills are the sweet spot: persistent storage + on-demand loading + human-editable.

Practical Significance

Say you want Claude to learn your company’s code standards:

Traditional approach:

1. Collect company codebase as training data
2. Prepare fine-tuning scripts and infrastructure
3. Run LoRA fine-tuning (requires GPU)
4. Deploy custom model
5. Cost: $1000+ and weeks of time

Skills approach:

# skills/company-standards/SKILL.md
---
name: company-standards
description: Company code standards and best practices
---

## Naming Conventions
- Function names use lowercase_with_underscores
- Class names use PascalCase
...
Cost: $0, Time: 5 minutes

This is the power of knowledge externalization: turn knowledge that requires training to encode into documents anyone can edit.

Problem Background

v3 gave us subagents to break down tasks. But there’s a deeper problem: how does the model know how to handle domain-specific tasks?

  • Processing PDFs? Need to know whether to use pdftotext or PyMuPDF
  • Building MCP servers? Need to know protocol specifications and best practices
  • Code review? Need a systematic checklist

This knowledge isn’t tools—it’s expertise. Skills solve this by letting the model load domain knowledge on-demand.

Core Concepts

1. Tools vs Skills

ConceptWhat it isExamples
ToolWhat the model can dobash, read_file, write_file
SkillWhat the model knows how to doPDF processing, MCP building

Tools are capabilities, skills are knowledge.

2. Progressive Disclosure

Layer 1: Metadata (always loaded)     ~100 tokens/skill
         └─ name + description

Layer 2: SKILL.md body (on trigger)   ~2000 tokens
         └─ Detailed guide

Layer 3: Resource files (on-demand)   Unlimited
         └─ scripts/, references/, assets/

This keeps context lightweight while allowing arbitrary depth of knowledge.

3. SKILL.md Standard

skills/
├── pdf/
│   └── SKILL.md          # Required
├── mcp-builder/
│   ├── SKILL.md
│   └── references/       # Optional
└── code-review/
    ├── SKILL.md
    └── scripts/          # Optional

SKILL.md format: YAML frontmatter + Markdown body

---
name: pdf
description: Process PDF files. Use for reading, creating, or merging PDFs.
---

# PDF Processing Skills

## Reading PDFs

Use pdftotext for quick extraction:
\`\`\`bash
pdftotext input.pdf -
\`\`\`
...

Core Code Implementation

#!/usr/bin/env python3
"""
v4_skills_agent.py - Mini Claude Code: Skills Mechanism (~550 lines)

Core Philosophy: "Knowledge Externalization"
============================================
v3 gave us subagents for task decomposition. But there's a deeper question:

    How does the model know HOW to handle domain-specific tasks?

The Paradigm Shift: Knowledge Externalization
--------------------------------------------
Traditional AI: Knowledge locked in model parameters
  - To teach new skills: collect data -> train -> deploy
  - Cost: $10K-$1M+, Timeline: Weeks

Skills: Knowledge stored in editable files
  - To teach new skills: write a SKILL.md file
  - Cost: Free, Timeline: Minutes
"""

import os
import re
import subprocess
import sys
from pathlib import Path

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv(override=True)


# =============================================================================
# Configuration
# =============================================================================

WORKDIR = Path.cwd()
SKILLS_DIR = WORKDIR / "skills"
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.getenv("MODEL_ID", "claude-sonnet-4-5-20250929")


# =============================================================================
# SkillLoader - The core addition in v4
# =============================================================================

class SkillLoader:
    """
    Manages loading and parsing of skill files.

    Key Design Decisions:
    --------------------
    1. Progressive disclosure: Metadata always loaded, body on demand
    2. Standard format: YAML frontmatter + Markdown body
    3. File-based: Git-friendly, human-readable, editable
    """

    def __init__(self, skills_dir: Path):
        self.skills_dir = skills_dir
        self.skills = {}
        self.load_skills()

    def load_skills(self):
        """Discover and load all skill metadata."""
        if not self.skills_dir.exists():
            return

        for skill_path in self.skills_dir.glob("*/SKILL.md"):
            skill = self.parse_skill_md(skill_path)
            if skill:
                self.skills[skill["name"]] = skill

    def parse_skill_md(self, path: Path) -> dict:
        """
        Parse YAML frontmatter + Markdown body.

        Returns:
            {name, description, body, path, dir}
        """
        content = path.read_text()

        # Match YAML frontmatter
        match = re.match(r'^---\s*\n(.*?)\n---\s*\n(.*)$', content, re.DOTALL)
        if not match:
            return None

        frontmatter_text, body = match.groups()

        # Parse YAML frontmatter
        frontmatter = {}
        for line in frontmatter_text.strip().split('\n'):
            if ':' in line:
                key, value = line.split(':', 1)
                frontmatter[key.strip()] = value.strip()

        return {
            "name": frontmatter.get("name", path.parent.name),
            "description": frontmatter.get("description", ""),
            "body": body.strip(),
            "path": path,
            "dir": path.parent
        }

    def get_descriptions(self) -> str:
        """
        Generate metadata list for system prompt.

        This is always loaded (Layer 1: ~100 tokens/skill).
        """
        if not self.skills:
            return "No skills available."

        return "\n".join(
            f"- {name}: {skill['description']}"
            for name, skill in self.skills.items()
        )

    def get_skill_content(self, name: str) -> str:
        """
        Get full skill content for context injection.

        This is loaded on demand (Layer 2: ~2000 tokens).
        """
        if name not in self.skills:
            return f"Skill '{name}' not found."

        skill = self.skills[name]
        return f"# Skill: {name}\n\n{skill['body']}"


# Global skill loader instance
SKILLS = SkillLoader(SKILLS_DIR)


# =============================================================================
# System Prompt - Updated for v4
# =============================================================================

SYSTEM = f"""You are a coding agent at {WORKDIR}.

Loop: plan -> act with tools -> update todos -> report.

Rules:
- Use TodoWrite to track multi-step tasks
- Use Skill tool to load domain expertise when needed
- Prefer tools over prose. Act, don't just explain.
- After finishing, summarize what changed.

Available Skills:
{SKILLS.get_descriptions()}"""


# =============================================================================
# Skill Tool - Loads domain knowledge on demand
# =============================================================================

TOOLS = [
    # v1-v3 tools (unchanged)
    # ... bash, read_file, write_file, edit_file, TodoWrite, Task

    # NEW in v4: Skill tool
    {
        "name": "Skill",
        "description": "Load a skill to get domain expertise.",
        "input_schema": {
            "type": "object",
            "properties": {
                "skill": {
                    "type": "string",
                    "description": "Name of the skill to load"
                }
            },
            "required": ["skill"],
        },
    },
]


# =============================================================================
# Tool Execution with Skill Support
# =============================================================================

def run_skill(skill_name: str) -> str:
    """
    Load and inject skill content into conversation.

    Key insight: Skill content enters as tool_result (user message),
    not system prompt. This preserves cache!
    """
    content = SKILLS.get_skill_content(skill_name)

    # Return as tool result - becomes part of conversation history
    return f"""<skill-loaded name="{skill_name}">
{content}
</skill-loaded>

Follow the instructions in the skill above."""


def execute_tool(name: str, args: dict) -> str:
    """Dispatch tool call to implementation."""
    if name == "Skill":
        return run_skill(args["skill"])
    # ... (other tools from v1-v3)
    return f"Unknown tool: {name}"

Message Injection (Preserving Cache)

Key insight: Skill content enters as tool_result (part of user message), not system prompt:

def run_skill(skill_name: str) -> str:
    content = SKILLS.get_skill_content(skill_name)
    # Full content returned as tool_result
    # Becomes part of conversation history (user message)
    return f"""<skill-loaded name="{skill_name}">
{content}
</skill-loaded>

Follow the instructions in the skill above."""

def agent_loop(messages: list) -> list:
    while True:
        response = client.messages.create(
            model=MODEL,
            system=SYSTEM,  # Never changes - cache stays valid!
            messages=messages,
            tools=ALL_TOOLS,
        )
        # Skill content enters messages as tool_result...

Key insight:

  • Skill content is appended as a new message at the end
  • All previous content (system prompt + history) is cache-reused
  • Only the newly appended skill content needs computation, the entire prefix hits cache

Comparison with Production

MechanismClaude Code / Kodev4
FormatSKILL.md (YAML + MD)Same
LoadingContainer APISkillLoader class
TriggerAuto + Skill toolSkill tool only
InjectionnewMessages (user message)tool_result (user message)
Cache mechanismAppend at end, prefix all cachedAppend at end, prefix all cached
Version controlSkill Versions APIOmitted
Permissionsallowed-tools fieldOmitted

Key commonality: Both inject skill content into conversation history (not system prompt), keeping prompt cache valid.

Why This Matters: Caching and Cost

Autoregressive Models and KV Cache

Large models are autoregressive: generating each token requires attending to all previous tokens. To avoid recomputation, providers implement KV Cache:

Request 1: [System, User1, Asst1, User2]
        ←────── All computed ──────→

Request 2: [System, User1, Asst1, User2, Asst2, User3]
        ←────── Cache hit ──────→ ←─ New compute ─→
               (cheaper)            (normal price)

Cache hit requires identical prefix.

Patterns to Watch

OperationImpactResult
Edit historyChanges prefixCache cannot be reused
Insert in middleSubsequent prefix changesNeed recompute
Modify system promptFront changesEntire prefix needs recompute

Recommendation: Append Only

# Avoid: Edit history
messages[2]["content"] = "edited"  # Cache invalid

# Recommend: Append only
messages.append(new_msg)  # Prefix unchanged, cache hit

Long Context Support

Mainstream models support large context windows:

  • Claude Sonnet 4.5 / Opus 4.5: 200K
  • GPT-5.2: 256K+
  • Gemini 3 Flash/Pro: 1M

200K tokens ≈ 150K words, a 500-page book. For most Agent tasks, current context windows are sufficient.

Treat context as an append-only log, not an editable document.

Series Summary

VersionThemeLines AddedCore Insight
v1Model as Agent~200Model is 80%, code is just the loop
v2Structured planning~100Todo makes plans visible
v3Divide and conquer~150Subagents isolate context
v4Domain expert~100Skills inject professional knowledge

Tools let the model do things, skills let the model know how.