Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

README.md

s07: Skill Loading — 用到的时候才加载

中文 · English · 日本語

s01 → s02 → s03 → s04 → s05 → s06 → s07s08 → s09 → ... → s19

"用到时再加载, 别全塞 prompt 里" — 通过 tool_result 注入, 不塞 system prompt。

Harness 层: 知识 — 按需加载, 不堆满上下文。


问题

你的项目有一套 React 组件规范、一份 SQL 风格指南、一份 API 设计文档。你希望 Agent 自动遵守这些规范。最直接的想法——全塞进 system prompt:

SYSTEM = (
    f"You are a coding agent. "
    + open("docs/react-style.md").read()       # 2000 行
    + open("docs/sql-style.md").read()         # 1500 行
    + open("docs/api-design.md").read()        # 3000 行
)

6500 行 system prompt。Agent 每次调用 LLM 都带着这些文档——不管是在改 CSS 颜色还是修 SQL 查询。99% 的内容和当前任务无关,白白消耗 token。


解决方案

Skill Overview

s06 的循环、钩子、TODO、子 Agent 全部保留。唯一的变化:启动时把技能目录注入 SYSTEM prompt,运行时多注册一个工具 load_skill(加载完整内容,用到才花 token)。

两层设计:

位置 时机 代价
1. 目录 system prompt 启动时注入(harness 扫描 skills/) ~100 tokens/skill,每轮都带
2. 内容 tool_result Agent 调用 load_skill 时 ~2000 tokens/skill,按需

循环一行不改。load_skill 自动通过 TOOL_HANDLERS[block.name] 分发。


工作原理

skills/ 目录——每个技能一个子目录,包含 SKILL.md 文件:

skills/
  agent-builder/SKILL.md
  code-review/SKILL.md
  mcp-builder/SKILL.md
  pdf/SKILL.md

第一级:启动时注入目录——harness 扫描 skills/ 目录,把每个技能的名字和一句话简介写进 SYSTEM prompt。Agent 每轮都能看到"我有哪些技能可用",不花额外 API 调用:

def build_system() -> str:
    catalog = list_skills()  # scan skills/ dir
    return (
        f"You are a coding agent at {WORKDIR}. "
        f"Skills available:\n{catalog}\n"
        "Use load_skill to get full details when needed."
    )

SYSTEM = build_system()  # runs once at startup

第二级:load_skill——Agent 决定"我需要 SQL 风格指南",调用 load_skill("sql-style")。内容通过 tool_result 注入,和读文件一模一样:

def load_skill(name: str) -> str:
    manifest = SKILLS_DIR / name / "SKILL.md"
    if not manifest.exists():
        return f"Skill not found: {name}"
    return manifest.read_text()

关键区别:技能内容通过 tool_result 注入,不是 system prompt。Agent 在当前对话中看到内容,但下次 LLM 调用时不自动携带。需要的话重新加载。

这就像你不会把三本参考书一直摊在桌上——你把它们放在书架上,用到哪本抽哪本。


相对 s06 的变更

组件 之前 (s06) 之后 (s07)
工具数量 7 (bash, read, write, edit, glob, todo_write, task) 8 (+load_skill)
知识加载 两级:启动时目录注入 SYSTEM + 运行时 load_skill
SYSTEM 提示 静态字符串 启动时扫描 skills/ 注入目录
循环 不变 不变(skill 工具自动分发)

试一下

cd learn-claude-code
python s07_skill_loading/code.py

试试这些 prompt:

  1. What skills are available?(应该直接从 SYSTEM prompt 里的目录回答,不调工具)
  2. Load the code-review skill and follow its instructions(应该调 load_skill)
  3. I need to do a code review -- load the relevant skill first

观察重点:Agent 是否直接从 SYSTEM 里的目录知道有哪些技能?它在需要具体规范时主动调了 load_skill 吗?system prompt 里有没有出现 skill 的完整内容?


接下来

按需加载解决了"不该带的不要带"。但另一个问题来了——该丢的怎么丢。Agent 连续工作 30 分钟后,messages 列表塞满了中间过程。旧的 tool_result、过时的文件内容——占着上下文但不产生价值。

s08 Context Compact → 四层压缩策略。便宜的先跑,贵的后跑。

深入 CC 源码

以下基于 CC 源码 loadSkillsDir.ts(1087 行)、SkillTool.tsbundledSkills.ts 的完整分析。

一、技能来源:不是只有一个 skills/ 目录

教学版假设所有技能在 skills/ 目录下。CC 实际从 10 个来源加载(loadSkillsDir.ts:638-1058):managed/policy skills、user skills(~/.claude/skills/)、project skills(.claude/skills/)、--add-dir skills、legacy commands(.claude/commands/)、dynamic skills、conditional skills、bundled skills、plugin skills、MCP skills。

二、SKILL.md Frontmatter 完整字段

CC 的 SKILL.md YAML frontmatter(loadSkillsDir.ts:185-265)有 16 个字段:

字段 用途
name / description 显示名称和描述
when_to_use 指导模型何时调用
allowed-tools 技能可用工具的自动允许列表
context inline(默认)或 fork(作为子 Agent 运行)
model 模型覆盖(haiku/sonnet/opus/inherit)
hooks 技能级别的 hook 配置
paths 条件激活的 glob 模式
user-invocable 用户可以通过 /name 调用

三、两级加载的精确实现

  1. Catalog(启动时)getSkillDirCommands() 扫描目录 → 注册为 Command 对象,只包含元数据。getSkillListingAttachments() 把技能列表格式化为附件,预算为上下文窗口的 ~1%(上限 8000 字符)。
  2. Load(调用时):模型调 Skill 工具 → getPromptForCommand() 展开完整 SKILL.md 内容 → 通过 tool_result 的 newMessages 注入对话。

教学版的简化是刻意的

  • 10 个来源 → 1 个 skills/ 目录:足以展示两级加载的核心概念
  • 16 个 frontmatter 字段 → 只读第一行作为简介:减少解析复杂度
  • forked skills(context: 'fork')→ 省略:子 Agent 的技能注入留给 s13