本章为 s07。

s07:Skill Loading(技能加载)

两级知识注入:Two-Level Knowledge Injection

痛点:如果把所有技能的详细说明(可能超过 8000 tokens)全部塞进 SYSTEM 提示词,Agent 的上下文窗口会被迅速撑爆,且成本高昂。

方案:将知识分为"轻量级目录"和"重量级内容"两级。

  1. 第 1 级(始终存在):在 SYSTEM 提示词中只注入一个技能目录,仅包含技能名称和一句话描述(约 100 tokens/技能)。让模型知道有哪些技能可用。
  2. 第 2 级(按需加载):当模型判断需要某个技能时,它调用 load_skill("技能名") 工具。系统从本地文件读取完整的 SKILL.md 内容,并通过 tool_result 注入到当前对话中。让模型在需要时才获取详细指令。

元数据驱动与注册表模式:Metadata-Driven & Registry Pattern

  • 技能被定义为 skills/ 目录下的子文件夹,每个文件夹内有一个 SKILL.md 文件。
  • SKILL.md 使用 YAML Frontmatter 声明元数据(如名称、描述),让技能本身是自描述的。
  • 程序启动时,_scan_skills() 遍历 skills/ 目录,解析所有 SKILL.md,构建一个包含所有技能元数据和完整内容的 SKILL_REGISTRY(注册表)。这是一个内存中的中央知识库。

动态的系统提示词:Dynamic System Prompt

SYSTEM 提示词不再是固定的字符串,而是由 build_system() 函数动态生成。它在启动时扫描技能目录,将最新的技能列表注入其中。这意味着只需在文件系统中增删技能文件夹,无需修改任何代码

技能文件解析与注册表构建

import yaml
SKILL_REGISTRY: dict[str, dict] = {}
# 标准的 frontmatter 解析器,输入 skill.md 文件的全部文本,返回 dict 类型的 meta 元数据和 str 类型的正文
def _parse_frontmatter(text: str) -> tuple[dict, str]:
    """解析 SKILL.md 的 YAML Frontmatter。返回 (元数据, 正文)。"""
    if not text.startswith("---"):
        return {}, text
    # 分隔符为 ---,最多进行两次分割
    parts = text.split("---", 2)
    if len(parts) < 3:
        return {}, text
    try:
        meta = yaml.safe_load(parts[1]) or {}
    except yaml.YAMLError:
        meta = {}
    return meta, parts[2].strip()
# 注册表构建:遍历 `SKILLS_DIR` 下的每个文件夹,查找 `SKILL.md` 文件。找到后,读取内容并解析,
# 最终构建一个 SKILL_REGISTRY 字典。键是技能名,值是包含 name、description、content(完整原始内容)的字典。
# 这个注册表就是 Agent 的"知识库"索引。
def _scan_skills():
    """扫描 skills/ 目录,填充 SKILL_REGISTRY。"""
    if not SKILLS_DIR.exists():
        return
    for d in sorted(SKILLS_DIR.iterdir()):
        if not d.is_dir():
            continue
        manifest = d / "SKILL.md"
        if manifest.exists():
            raw = manifest.read_text()
            meta, body = _parse_frontmatter(raw)
            name = meta.get("name", d.name)
            desc = meta.get("description", raw.split("\n")[0].lstrip("#").strip())
            SKILL_REGISTRY[name] = {"name": name, "description": desc, "content": raw}
_scan_skills()

动态构建系统提示词

# 遍历全局的 SKILL_REGISTRY,生成人类可读、模型也易理解的 Markdown 格式技能清单。
def list_skills() -> str:
    """列出所有技能(名称 + 一行描述)。"""
    if not SKILL_REGISTRY:
        return "(no skills found)"
    return "\n".join(f"- **{s['name']}**: {s['description']}" for s in SKILL_REGISTRY.values())
# 该函数在模块加载时被调用一次
def build_system() -> str:
    """构建包含技能目录的 SYSTEM 提示词。"""
    catalog = list_skills()
    return (
        f"You are a coding agent at {WORKDIR}. "
        f"Skills available:\n{catalog}\n"
        # 需要 load_skill 加载详细指令
        "Use load_skill to get full details when needed."
    )
SYSTEM = build_system()

按需加载技能的工具函数

def load_skill(name: str) -> str:
    """按名称加载完整技能内容。通过注册表查找,无路径遍历风险。"""
    skill = SKILL_REGISTRY.get(name)
    if not skill:
        return f"Skill not found: {name}"
    return skill["content"]

技能工具集成到 Agent 循环

TOOLS 列表中添加一个新的工具定义,告诉 API 这个工具存在,作用是"通过名称加载技能的完整内容",且需要一个 name 参数。

# 在工具定义列表 TOOLS 中新增
TOOLS = [
    # ... 其他工具 ...
    {
        "name": "load_skill",
        "description": "Load the full content of a skill by name.",
        "input_schema": {
            "type": "object",
            "properties": {"name": {"type": "string"}},
            "required": ["name"]
        }
    },
]
# 在工具处理函数映射 TOOL_HANDLERS 中注册
TOOL_HANDLERS = {
    # ... 其他处理器 ...
    "load_skill": load_skill,
}

工作原理(完整时序)

ps:子代理没有 skill 技能,也没有 task 工具。

从社区获取 skill 或自己创建 skill,SKILL.md 是核心文件,里面是技能的完整说明。完整流程:

  1. 启动时:扫描 skills/ 并构建技能注册表 SKILL_REGISTRY
  2. 构建 SYSTEM 提示词:第一级注入——把轻量技能目录写进 SYSTEM(如 build_system() 生成的清单)。
  3. 用户请求(如"帮我做代码审查"):用户的问题进入主对话。
  4. Agent 循环开始:模型看到 SYSTEM 中的技能目录,知道有 code-review 这个技能可用。
  5. 模型决定加载技能:第二级注入——模型调用 load_skill("code-review")
  6. 系统执行 load_skill:从注册表取出完整 SKILL.md 内容,通过 tool_result 返回给模型。
  7. 下一轮:模型现在拥有了完整的代码审查知识,会按照 SKILL.md 中定义的检查清单和流程来工作。

技能加载后的两种执行方式

技能内容返回后,模型有两种使用方式:

方式 说明 适用场景
① 作为指令注入 技能内容本身是指令,模型加载后在后续对话中遵循这些指令行动 大多数 skill(如 code-review 检查清单)
② 作为可执行代码 通过 exec() 动态加载函数,把函数注册为额外工具,扩展 Agent 能力 需要真正运行逻辑的 skill(稍复杂)

① 作为指令注入

skill_content = SKILL_REGISTRY["code-review"]["content"]
# 这个完整的 SKILL.md 被注入到对话中
history.append({"role": "user", "content": [{
    "type": "tool_result",
    "tool_use_id": "...",
    "content": skill_content  # 完整的审查指南
}]})

② 作为可执行的代码(稍微复杂一点)

通过 exec() 动态加载函数,这些函数作为额外的工具注册到 Agent 工具池中……

好的 Skill 设计核心

  • 明确元数据(YAML Frontmatter)
---
name: skill-name
description: One-line summary for the catalog
---
  • 清晰工作流
## Process
1. Step one
2. Step two
  • 具体输出格式
## Output Format
[明确告诉模型应该输出什么格式]
  • 示例和边界条件
## Examples
## Edge Cases
## What NOT to do

本模块知识点

  • 两级知识注入:SYSTEM 只放轻量技能目录(~100 tokens/技能),load_skill 按需注入完整 SKILL.md
  • 注册表模式_scan_skills() 扫描 skills/ 构建内存 SKILL_REGISTRY(元数据 + 完整内容)。
  • 动态 SYSTEMbuild_system() 启动时扫描技能目录生成提示词,增删技能文件夹无需改代码。
  • SKILL.md 用 YAML Frontmatter 自描述;ps:子代理无 skill、无 task。