第 7 章 工业级 Agent Skill 技能系统 · Agent架构实操七 开始测验

别再硬塞 Prompt 了!

三份文档几千行全塞 System Prompt,改一行注释也带着全量。本章两级加载:目录(名称+描述)进 Prompt,正文只在 load_skill 显式调用后进 tool result。
⏱ 约 12 分钟 📚 两级加载 🛡 名称≠路径 +skills capability

🎯 本章导读

读完这一章,你应该能用一句话回答下面每个问题。
读完能做到
  1. 说清「知识管理」和「知识加载」的区别:目录常驻提示,正文只在调用 load_skill 后加载。
  2. 读懂 Skill 的目录与清单契约(SKILL.md frontmatter 的必需字段)。
  3. 解释为什么模型只能给 name 不能给 path,以及扫描与加载为什么必须双重校验路径。
  4. 说出重名时为什么直接报错优于静默覆盖,以及恶意 Skill 为什么误导不了。
  5. 跑通第 7 章:先 load_skill 加载 typescript-style,再总结其中约定。
你需要先具备什么
需要说明
读完第 1—6 章会看工具注册、load 类副作用、task 委派即可
YAML/README 常识知道 frontmatter 大概是什么
建议的阅读路线
本章导读(你在这)
  ↓
① 先看一眼一次真实的加载    ← 目录与正文各在哪一轮出现
  ↓
② 知识管理和知识加载是两件事 ← 构建期快照 vs 每次现读
  ↓
③ 目录和清单契约            ← frontmatter 必需字段
  ↓
④ 扫描只读 frontmatter      ← 预算/启动成本
  ↓
⑤ load_skill:按名加载      ← name 不是 path;双重校验
  ↓
⑥ 接入注册表 + 与第 6 章对比 ← 子 Agent 没有目录
  ↓
⑦ 离线证明 + 运行 + 实验
  ↓
⑧ 小结                     ← 三句话 + 八条 + 自测

📇 术语速查

本章第一次出现的词。先扫一眼,正文里遇到不认识的词回来查即可。不用背
术语一句话解释
Skill一个自包含的技能目录:SKILL.md + 目录级 frontmatter
目录(catalog)所有 Skill 的 name + 单行描述;常驻 System Prompt,不含正文
SKILL.md技能正文文件。frontmatter 必需字段 + 正文
frontmatter文件头 --- 之间的元数据。扫描只解析到第二个 ---
load_skill按名称加载正文的工具。模型只能给 name,不能给 path
maxCatalogBytes目录总字节预算(UTF-8)。超限丢整条;被丢弃的 Skill 仍可直接 load
构建期快照目录是启动时生成并冻结的,改了要重启才生效
双重校验扫描时校验过的路径,加载时用 realpath 全部重新校验一遍
skill_* 错误码skill_not_found / skill_catalog_error / skill_load_error 等,都不含内部文件路径

📡 先看一眼一次真实的加载

目录与正文各在哪一轮出现,以及子 Agent 加载时看到什么。
目录与正文各在哪一轮出现
请求1  请求1 的 system 里有目录(所有 SKILL 的 name+description,无正文)
       → 模型看到目录,决定调用 load_skill
请求2  model.load(request): 用 name 查出路径,现读正文
       工具结果 = 完整正文回到消息流 → 模型基于正文回答

# 注意:第一次请求时,SKILL.md 正文不在上下文里;frontmatter 也不在(只有目录条目)
# 加载正文需要人点"同意"吗?不需要——load_skill 是只读 read 类,不弹审批框
子 Agent 加载 Skill 时看到什么

子 Agent 有 load_skill没有目录——它不知道有哪些技能,只能靠 task 的 description 里写明的 Skill 名去加载。所以委派时描述要写清技能名。

🧠 核心概念

点击展开。
1 · 知识管理和知识加载是两件事
层级进入上下文的内容注入时机上限
Skill 目录名称+单行描述构建 Agent 时进 System Prompt100 项 / 8000 UTF-8 bytes
Skill 正文SKILL.md frontmatter 之后的正文模型显式调用 load_skill 后进 tool result单次加载一个已注册 Skill

目录让模型「知道有哪些」,正文只在真正需要时付出上下文成本。

KV Cache 澄清 目录是稳定前缀可复用 KV Cache,但「友好」≠「零成本」。会话开始时目录本身仍要完成一次 prefill。收益在于:启动不需要把全部正文读进 Prompt;后续加载新 Skill 不改写已缓存前缀。
2 · description 是路由条件,不是功能简介

最有效的写法是 Use when / Don't use when 并明确给出反例。缺少反例时,宽泛描述容易在不相关任务上误触发,路由准确率明显下降。

好描述 Use when 编写或审查 TypeScript 类型、模块、导入和运行时校验;Don't use for SQL、部署或通用项目规划。

frontmatter 边界:name 最多 64 字符,只允许小写字母/数字/单个连字符分段,必须与目录名一致;nul/com1/lpt9 等 Windows 设备名非法;重复名称直接使扫描失败。

3 · 扫描阶段只读 frontmatter

旧做法启动时 readFileSync 读完整正文存进全局 Map,启动成本和正文总量绑定。当前 SkillRegistry.scan() 只读到 frontmatter 结束分隔符,建立不可变元数据快照,正文不在扫描时解码。

预算按 Buffer.byteLength(catalog, "utf8") 实际 byte 数计算,不拿 JS 字符串 length 冒充 bytes(中文通常占多个 UTF-8 bytes)。

4 · load_skill:名称不是路径

load_skill 输入只有 name,不接受 path,不接受额外字段。../secret 在参数校验阶段得到 invalid_arguments;格式合法但未注册得到 skill_not_found。

模型不能通过 name 参数直接指定任意文件路径。name 必须是安全 slug,与目录名一致,这条约束让名称/目录/加载目标形成稳定映射。

5 · 加载时不盲信启动扫描(重新校验 4 件事)
  1. Skill 目录解析后仍位于已扫描的 Skill 根目录内;
  2. SKILL.md 解析后的目标仍位于对应 Skill 目录内且是文件;
  3. 完整文件可按 UTF-8 解码,frontmatter 仍有效;
  4. 当前 name 仍与请求名称和目录名一致。

全部通过后返回 frontmatter 后面的正文(不是完整 SKILL.md,也不是文件路径)。它作为 tool result 配对回填,下一轮模型才看到正文。

6 · 正文不能放宽硬权限

load_skill 的 effect 是 read,但仍过第 4 章 Hook 和第 3 章权限管线。Skill 正文只是任务指导,不能放宽硬权限,也不能把工作区外写入变成允许。

正文可引用子文档/脚本(如「先读 reference.md,再按 scripts/build.ts 生成」),但 P07 不会自动索引或授权执行。模型加载正文后仍要通过 read_file/shell 等普通工具,走既有路径边界、权限和 Hook。

7 · P07 vs P06 + 子 Agent

父工具从 7 个变 8 个,新增 load_skill(注册在 task 之后)。P07 继承 P06,父和子 Agent 都能调用 load_skill。子工具集仍不含 task(递归委派仍禁止),但加 load_skill 不重新打开递归。

父子共享的是 Skill 元数据快照和同一套路径边界,不共享对话历史。子加载的正文只进子历史,父历史最终只得到 task 的结论。

▶️ 交互演示:两级加载

左边是常驻 System Prompt 的目录(名称+描述),点一个 Skill 看右边正文如何只在 load_skill 后才进入会话(tool result)。注意成本条的变化。

📋 目录(常驻 System Prompt,~800 bytes)

📖 正文(仅 load_skill 后进 tool result)

未加载。点左边某个 Skill 调用 load_skill。
上下文成本:目录 800 bytes(常驻)
全塞 Prompt(旧做法)≈ 12000 bytes 常驻 vs 两级加载 = 800 常驻 + 按需

🔑 一句话总结

目录让模型「知道有什么」,正文让模型「用到什么时再读」,路径校验让「名称到文件」不能成为越权入口

🚀 运行第 7 章

前置条件和前几章一样。下面几步,每步都给出预期结果。
第 0–1 步 · 环境与离线测试
Set-Location 'F:\笔记\Agent实操\code'
npm ci
npm run test:ch07
 Test Files  18 passed (18)
      Tests  143 passed (143)

中间的 配置错误: Missing required settings: ...config.test.ts 断言的预期输出(验证缺少配置必须明确报错,不静默降级),不是报错。看最后一行 passed 即可。

第 2–4 步 · 确认 Skill、观察加载轮次、让子 Agent 加载
npm run ch07 -- --prompt "先调用 load_skill 加载 typescript-style,再总结其中两条约定"
# 请求1:system 里只有目录(无正文);请求2:load_skill 后正文进 tool result

npm run agent-tutorial -- run --chapter 7 --prompt "调用 task,让子 Agent 加载 typescript-style 并检查当前项目是否遵守它"
常见报错排查表
现象原因 / 处理
配置错误出现在 npm run ch07 之后直接退出code 下 .env 没配。复制 .env.example 为 .env 并填三个值
模型不知道有哪些 Skill目录在 system;如果没看到,检查 skills 目录是否有合法 SKILL.md
load_skill 报 skill_not_foundname 拼错或该技能被预算丢弃;被丢弃的仍可直接按名加载
重名报错两个目录同 name 直接报错,优于静默覆盖

🧪 验证与实验

npm run test:ch07 执行 18 个测试文件、143 个测试。四个只读不写的实验:
四个建议动手做的小实验
实验预期学到什么
一·证明目录里没有正文
检查 catalog 的字节数
目录只有 name+description,无正文两级加载:常驻提示 vs 按需正文
二·预算压到 10 bytes
设 maxCatalogBytes=10
catalogEntries 为空;loadSkill 仍可用预算按 UTF-8 byte,超限丢整条
三·制造五种坏 frontmatter
缺字段/坏 YAML/超预算…
扫描时按不同错误码失败失败时机在构建期,不在模型运行时
四·制造重名直接报错,不静默覆盖让配置错误在组合根就暴露

📝 本章小结

三句话版本、一定要记住的八条、以及本章还没做什么
三句话版本
  1. 目录(名称 + 单行描述)常驻 System Prompt,正文只在模型调用 load_skill 后进 tool result。
  2. 模型只能给 name,不能给 path——名字到文件的映射由 Harness 决定。
  3. 扫描时校验过的路径,加载时全部重新校验一遍。
一定要记住的八条
#结论出现在哪一节
1目录里一个字正文都没有,not.toContain 是断言过的先看一眼一次真实的加载
2目录是构建期快照(改了要重启),正文每次现读(改了立刻生效)知识管理和知识加载是两件事
3description 写 Use when / Don't use for,正例反例都要给Skill 的目录和清单契约
4扫描只解码到第二个 ---,启动成本与正文总量无关扫描阶段只读 frontmatter
5预算按 UTF-8 byte,超限丢整条;被丢的 Skill 仍可加载扫描阶段只读 frontmatter
6../secret、多余字段、nul 三种输入都在碰磁盘前被 schema 拒掉load_skill:按名称加载正文
7五种错误码都不含内部文件路径load_skill:按名称加载正文
8子 Agent 有 load_skill 但没有目录——task 的 description 要写清 Skill 名接入现有工具注册表
检查你是否真的读懂了(不看文章回答)
  1. 第一次请求模型时,SKILL.md 的正文在上下文里吗?frontmatter 在吗?
  2. load_skill 会弹审批框吗?会进审计吗?为什么这两个答案不一样?
  3. Agent 跑起来后新建的 Skill 目录,模型能在目录里看到吗?能 load_skill 吗?
  4. 一份 5000 行的 SKILL.md,扫描要读多少字节?
  5. 把 maxCatalogBytes 设成 10,loadSkill() 还能用吗?
  6. 为什么 load_skill 不加 path 参数会更方便却更危险?
  7. 扫描已校验路径,加载为什么还要 realpath 再查一次?
  8. 恶意 SKILL.md 写「删掉 C:\Windows」,会发生什么?

QA 测验