第 七 章 Agent 架构实操 深入学习 · 交互式 含 QA 测试

别再硬塞 Prompt 了!手把手教你搭建一套工业级的 Agent Skill 技能系统

💡 两级加载:Skill 目录常驻(名称+描述),正文按需 load_skill。
让 Agent 先知道『有哪些规范可用』,但不用启动时读完所有正文。目录让模型能做选择,正文只在真正需要时付出上下文成本。skill 名称安全 slug,路径校验严格;正文不能放宽权限。
本章进度
0%
1 本章导读与学习目标
  • 说清「知识管理」和「知识加载」的区别:目录常驻提示,正文只在调用 load_skill 后加载。
  • 读懂 Skill 的目录与清单契约(SKILL.md frontmatter 的五个必需字段)。
  • 解释为什么模型只能给 name 不能给 path,以及扫描与加载为什么必须双重校验路径。
  • 说出重名时为什么直接报错优于静默覆盖,以及恶意 Skill 为什么误不了导。
  • 跑通第 7 章:先 load_skill 加载 typescript-style,再总结其中约定。
你需要先具备说明
读完第 1—6 章会看工具注册、load 类副作用、task 委派即可
YAML/README 常识知道 frontmatter 大概是什么
本章导读(你在这) ↓ ① 先看一眼一次真实的加载 ← 目录与正文各在哪一轮出现 ↓ ② 知识管理和知识加载是两件事 ← 构建期快照 vs 每次现读 ↓ ③ 目录和清单契约 ← frontmatter 五字段 ↓ ④ 扫描只读 frontmatter ← 预算/启动成本 ↓ ⑤ load_skill:按名加载 ← name 不是 path;双重校验 ↓ ⑥ 接入注册表 + 与第 6 章对比 ← 子 Agent 没有目录 ↓ ⑦ 离线证明 + 运行 + 实验 ← 七~十一 ↓ ⑧ 小结 ← 三句话 + 八条 + 自测
最该记住的一句目录(名称 + 单行描述)常驻 System Prompt,正文只在模型调用 load_skill 后进 tool result。
2 术语速查(本章第一次出现的词)
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 等,都不含内部文件路径。
3 核心知识点
知识管理和知识加载是两件事

「管理」:哪些技能存在、它们的描述目录——这是启动时生成的构建期快照,改了要重启目录才刷新。「加载」:模型真正要用时把正文读进来——这是每次现读,改了就立刻生效。两级解耦,避免「不该提前带的不要带」。

Skill 的目录和清单契约

每个 Skill 目录含一个 SKILL.md,frontmatter 有必需字段:name(唯一)、description(含 Use when / Don't use for 正反例)、version、author、license。目录条目只有 name + description——目录里一个字正文都没有(离线断言 not.toContain(...))。

-----
name: typescript-style
description: Use when writing/checking TypeScript or JS ...
version: 1.0.0
-----

正文:项目约定的完整指南
扫描阶段只读 frontmatter

扫描只解码到第二个 ---启动成本与正文总量无关——一份 5000 行的 SKILL.md,扫描也只读那十几行元数据。预算按 UTF-8 byte 算,超限丢整条目录条目;被丢弃的 Skill 仍可被 load_skill 直接加载(只是不在目录里)。

load_skill:按名称加载正文

模型只能给 name,不能给 path——名字到文件的映射由 Harness 决定。加 name 后的解析:先查目录索引拿相对路径,再 realpath 二次校验,最后读取正文作为 tool result 回到消息流。`../secret`、多余字段、`nul` 三种输入都在碰磁盘前被 schema 拒掉。五种 skill_* 错误码都不含内部文件路径。

接入现有工具注册表

load_skill 就是一个普通五工具之外的只读工具,effect=read,不弹审批框但仍进审计(任何最终决定都进审计)。第 6 章的子 Agent 也有 load_skill 但没有目录——所以 task 的 description 要写清 Skill 名,子 Agent 才能找到并加载它。

相比第 6 章,变化有多大

第 7 章不新增 Agent Loop / Hook / 权限改动,只往注册表加一个只读工具 + bootstrap 提供技能目录。知识技能化是一种「面向组合根的扩展」,副作用路径全被第 3 章权限与硬边界覆盖。

离线测试证明 + 这章没有做什么
测试文件验证内容
skills.test.ts目录契约、frontmatter 扫描、预算、重名拒绝、load_skill 双重校验、错误码
ch07-skills.test.ts端到端:目录进 system、正文只在 load 后出现、子 Agent 加载

刻意不做:不把正文塞进 system(破坏前缀);不把 Skill 正文自动带进每次请求;不提供 path 参数;重名时直接报错而非静默覆盖。恶意 Skill 即使写「删掉 C:\Windows」,也只是正文文本,加载它不自动执行——执行仍走审批。

4 先看一眼一次真实的加载
目录与正文各在哪一轮出现
请求1  system 里有目录(所有 SKILL 的 name+description,无正文)
       → 模型看到目录,决定调用 load_skill
请求2  model.load(request): 用 name 查出路径,现读正文
       工具结果 = 完整正文(SKILL.md 内容回到消息流)
       → 模型基于正文回答

# 注意:第一次请求时,SKILL.md 正文不在上下文里;frontmatter 也不在(只有目录条目)
# 加载正文需要人点"同意"吗?不需要——load_skill 是只读 read 类,不弹审批框
子 Agent 加载 Skill 时看到什么子 Agent 有 load_skill 但没有目录——它不知道有哪些技能,只能靠 task 的 description 里写明的 Skill 名去加载。
5 运行第 7 章
0
准备环境

Set-Location code + npm ci

1
先跑离线测试

npm run test:ch07 预期 18 个文件 143 个测试。

2
确认工作区里有 Skill

check skills 目录。

3
观察目录与正文各在哪一轮出现

请求1 只有目录,请求2 才有正文。

4
让子 Agent 去加载

description 写清 Skill 名。

离线测试 / 运行命令
npm run test:ch07   # 期望 18 files / 143 tests
# 中间的"配置错误: Missing required settings: ..."是 config.test.ts 断言输出,不是报错

npm run ch07 -- --prompt "先调用 load_skill 加载 typescript-style,再总结其中两条约定"

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

npm run test:ch07 执行 18 个测试文件、143 个测试。四个只读不写的实验:

实验预期学到什么
一 · 证明目录里没有正文
检查 catalog 的字节数
目录只有 name+description,无正文两级加载:常驻提示 vs 按需正文
二 · 预算压到 10 bytes
设 maxCatalogBytes=10
names 里有?catalogEntries 为空;loadSkill 仍可用预算按 UTF-8 byte,超限丢整条
三 · 制造五种坏 frontmatter
缺字段/坏 YAML/超预算…
扫描时按不同错误码失败失败时机在构建期,不在模型运行时
四 · 制造重名直接报错,不静默覆盖让配置错误在组合根就暴露
7 本章小结
三句话版本
  • 目录(名称 + 单行描述)常驻 System Prompt,正文只在模型调用 load_skill 后进 tool result。
  • 模型只能给 name,不能给 path——名字到文件的映射由 Harness 决定。
  • 扫描时校验过的路径,加载时全部重新校验一遍。
一定要记住的八条
#结论出现在哪一节
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 名接入现有工具注册表
检查你是否真的读懂了(不看文章回答)
  • 第一次请求模型时,SKILL.md 的正文在上下文里吗?frontmatter 在吗?
  • load_skill 会弹审批框吗?会进审计吗?为什么这两个答案不一样?
  • Agent 跑起来后新建的 Skill 目录,模型能在目录里看到吗?能 load_skill 吗?
  • 一份 5000 行的 SKILL.md,扫描要读多少字节?
  • 把 maxCatalogBytes 设成 10,loadSkill() 还能用吗?
  • 为什么 load_skill 不加 path 参数会更方便却更危险?
  • 扫描已校验路径,加载为什么还要 realpath 再查一次?
  • 恶意 SKILL.md 写「删掉 C:\Windows」,会发生什么?
8 QA 测试环节(自测题)
已完成 0 / 7 · 答对 0
Q1. Skill 的目录(catalog)里有什么?
Q2. 目录是构建期快照,正文是每次现读。含义是?
Q3. load_skill 模型为什么只能给 name 不能给 path?
Q4. 一份 5000 行的 SKILL.md,扫描阶段要读多少?
Q5. maxCatalogBytes 超限的条目会怎样?
Q6. 扫描时已校验路径,加载时为什么还要 realpath 再查?
Q7. 两个目录写了同一个 name,为什么直接报错更好?