· Agent 工程 ·阅读时长约 7 分钟

Skills 自动加载机制:Agent 如何"读取"Skills

关键点:Skill 不是"自动读取"的,而是模型"主动调用"的。这个"看起来自动"的效果,来自三方协作 —— Harness(运行时)注入元数据,模型判断相关性,工具机制加载正文。

Skill

所属专题:Skills 协议 (skills·02)

Skills 自动加载机制:Agent 如何”读取”Skills

关键点:Skill 不是”自动读取”的,而是模型”主动调用”的。这个”看起来自动”的效果,来自三方协作 —— Harness(运行时)注入元数据,模型判断相关性,工具机制加载正文


一、核心机制:三方协作

┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│   Harness   │      │     LLM     │      │  Filesystem │
│(Claude Code)│      │   (模型)    │      │ (skills目录)│
└─────────────┘      └─────────────┘      └─────────────┘
       │                    │                     │
   1. 扫描目录,读元数据 ─────────────────────────►│
       │                    │                     │
   2. 注入 skill 列表 ────►│                     │
       │                    │                     │
       │             3. 匹配 description          │
       │                (纯模型推理)              │
       │                    │                     │
       │      4. 调用 Skill 工具 ┌───────────┐    │
       │◄───────────────────┤ tool_use  │        │
       │                    └───────────┘        │
       │                                         │
   5. 读取 SKILL.md ─────────────────────────────►│
       │◄─────────────────────── 返回正文 ────────│
       │                                         │
   6. 作为 tool_result 返回 ►│                   │
       │                    │                    │
       │        7. 按 skill 指引执行             │

二、分步拆解实现

步骤 1:会话启动时扫描目录

Harness(Claude Code CLI)启动时:

# 伪代码
def load_skills():
    skills = []
    for path in ["~/.claude/skills/", ".claude/skills/", plugin_dirs]:
        for skill_dir in scan(path):
            skill_md = read(skill_dir / "SKILL.md")
            frontmatter = parse_yaml_header(skill_md)
            skills.append({
                "name": frontmatter["name"],
                "description": frontmatter["description"],
                "path": skill_dir,  # 记住路径,正文按需读
            })
    return skills

关键:只读 frontmatter,正文完全不加载。

步骤 2:把元数据注入 system prompt

Harness 在构造 LLM 请求时,把 skill 列表塞进 system message:

<system-reminder>
The following skills are available for use with the Skill tool:

- brainstorming: Use before any creative work...
- systematic-debugging: Use when encountering any bug...
- test-driven-development: Use when implementing any feature...
- deep-research: When the user wants a deep, multi-source report...
</system-reminder>

这就是你在对话开头看到的 available-skills 那段的来源。

步骤 3:暴露 Skill 工具给模型

同时,Harness 通过 Function Calling 机制注册一个 Skill 工具:

{
  "name": "Skill",
  "description": "Execute a skill within the main conversation",
  "input_schema": {
    "type": "object",
    "properties": {
      "skill": {"type": "string", "description": "The name of the skill"},
      "args": {"type": "string"}
    }
  }
}

现在模型知道:“我有一堆 skill,用 Skill 工具就能加载”

步骤 4:模型自主判断

用户发消息后,LLM 收到:

[system prompt]
[skill 列表]
[available tools: Skill, Read, Bash, ...]
[user] 帮我 debug 这个 TypeError

模型内部推理:

“用户说 debug —— systematic-debugging 的 description 匹配 —— 我应该调用 Skill 工具加载它”

这一步没有任何代码在”判断”,是模型自己决定的。 这就是为什么 description 写得好不好,直接决定 skill 能否被触发。

步骤 5:模型调用 Skill 工具

模型输出一个 tool_use:

{
  "type": "tool_use",
  "name": "Skill",
  "input": {"skill": "systematic-debugging"}
}

步骤 6:Harness 拦截、读文件、返回

Harness 收到 tool_use:

def handle_skill_tool(skill_name):
    skill = find_skill(skill_name)
    body = read_file(skill.path / "SKILL.md")
    return {"type": "tool_result", "content": body}

正文作为 tool_result 塞回下一轮 LLM 请求。

步骤 7:模型按指引行动

现在模型的上下文里有了完整 skill 正文,接下来的回复就会按照 skill 里的步骤走。


三、“自动”的假象从何而来

很多人以为是”自动读取”,其实是这两件事的错觉:

  1. 元数据常驻 → 每次对话模型都”看见”所有 skill 存在
  2. 模型判断快 → 匹配 + 调用工具在一次响应里完成,用户感受不到中间步骤

真相

  • Harness 不判断相关性,它只做”扫目录 + 提供工具 + 读文件”
  • LLM 不”自动加载”,它是主动调用 Skill 工具
  • 整个机制的智能部分完全在模型的 in-context reasoning

四、类比:图书馆借阅

现实Skill 机制
图书馆入口的索引卡片墙system prompt 里的 skill 列表
你(读者)扫一眼卡片决定借哪本模型判断哪个 skill 相关
拿卡片去柜台请馆员取书调用 Skill 工具
馆员从书架取书给你Harness 读 SKILL.md 返回
你按书上的方法做事模型按 skill 指引执行

没有一步是”自动”的,但整个流程流畅得像自动。


五、几个关键实现细节

1. 为什么 description 是”触发钥匙”

因为它是模型判断相关性的唯一线索(元数据阶段正文不可见)。写得模糊 → 模型不知道何时用 → skill 形同虚设。

好的 description 模板:

Use when [触发场景], before [某个行动]

2. 为什么可以扩展上百个 skill

  • 100 个 skill 元数据 ≈ 5000 token,占上下文比例小
  • 只有真正触发的才展开正文(几千 token)
  • 空间复杂度 O(元数据),而非 O(全部内容)

3. 为什么显式 /skill-name 也能触发

用户输入 /brainstorming 时,Harness 会:

  • 识别为 slash command
  • 直接构造一个 tool_use 让模型执行 Skill 工具
  • 相当于跳过模型判断,强制加载

4. Subagent 隔离

skill 里常写”如果你是 subagent,忽略这条”。因为 subagent 有独立上下文,它也会看到 skill 列表 —— 如果不加限制,会导致递归触发。

5. 系统提示注入

技术上,skill 列表通过 system-reminder tag 注入。这是 Anthropic 定义的一种特殊结构,模型被训练成”注意但不直接回应”这类消息 —— 保证 skill 列表不干扰对话流。


六、代码层实现要点(如果你要自己造)

若要在自研 Agent 里复刻这套机制,核心是四件事:

# 1. 启动时索引
skills = scan_skills_directory()  # 只读 frontmatter

# 2. 每轮请求注入元数据
system_prompt += format_skills_list(skills)

# 3. 注册工具
tools.append(SKILL_TOOL_SCHEMA)

# 4. 处理 tool_use
def on_tool_call(name, args):
    if name == "Skill":
        return read_file(skills[args["skill"]].path / "SKILL.md")

就这么简单。复杂度全在两个非代码的部分:

  • 写好 description(触发精准度)
  • 写好 SKILL.md 正文(执行有效性)

七、能力归属:LLM vs Harness 的边界

一个常见的理解误区是:“LLM 自己会读取 skills”。更精确的说法是:LLM 出厂不知道什么叫 skill,是 Harness 工程赋予了 Agent 使用 skills 的能力。

1. LLM 出厂时只有”通用原语”

LLM 训练时其实拥有两个通用能力,是 skill 机制得以运作的基础:

能力说明谁提供
Function Calling / Tool Use能输出结构化 tool_use 调用工具模型训练时具备
In-context Reasoning能根据 system prompt 里的信息做判断模型固有能力

“Skill”这个概念根本不存在于模型训练里 —— 它是应用层构造出来的。Harness 做的是利用这两个原语,搭建一套”skill 协议”

  • 用 in-context reasoning → 让模型判断哪个 skill 相关
  • 用 function calling → 让模型调用 Skill 工具加载正文

2. “自主”是模型的,“读取”是 Harness 的

“自主读取”其实是两件事的组合:

动作谁做的
决定要不要用某个 skillLLM 自主判断(推理)
实际读取磁盘上的 SKILL.mdHarness 执行(模型没有文件系统权限)

模型永远无法自己”读文件”,它只能请求 Harness 帮它读(通过 tool_use)。

3. 能力分层图

┌─────────────────────────────────────────────────────┐
│  LLM 出厂能力(通用原语)                            │
│  ├─ Function Calling                                │
│  ├─ In-context Reasoning                            │
│  └─ 文本生成                                        │
└─────────────────────────────────────────────────────┘

        Harness 工程在这些原语上加了一层协议

┌─────────────────────────────────────────────────────┐
│  Skill 协议(Harness 定义)                          │
│  ├─ 扫描 skill 目录                                  │
│  ├─ 注入元数据到 system prompt                       │
│  ├─ 注册 Skill 工具                                  │
│  └─ 拦截 tool_use、读文件、返回内容                  │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│  Agent 表现出的行为(用户视角)                       │
│  "Agent 自主读取并使用 skill"                        │
└─────────────────────────────────────────────────────┘

4. 一个反证

如果你直接调用 Anthropic API(不用 Claude Code 这类 Harness),把一段用户消息发给 Claude:

  • 模型完全不知道你机器上有哪些 skill
  • 也不会主动”搜索”任何 skill
  • 因为没有 Harness 帮它扫目录、注入元数据、暴露 Skill 工具

这说明 “skill 能力” 确实是应用层(Harness)赋予的,不是模型自带的。

5. 精确版理解

LLM 出厂只有”通用推理 + 工具调用”两个原语,“skill 机制”是 Harness 工程用这两个原语搭出来的一套协议。模型负责”判断用哪个”,Harness 负责”实际加载”,二者合作产生了”Agent 自主使用 skills”的表现。

一句话把边界收紧:决策自主,执行代劳。


八、一句话总结

Skills 的”自动”,本质是 “元数据常驻 + 模型自主决策 + 工具化延迟加载” 三者的组合。 Harness 只提供机制(发现 + 加载),智能全在模型


相关文档

  • [[03-skills原理]] —— Skills 的基本原理与分层结构
  • [[02-agent开发框架]] —— 主流 Agent 框架全景

评论