
字节笔记本
2026年10月24日 · 约 11 分钟读完
渐进式披露:Skills 怎么组织 Agent 能力

本文是《从 0 写 Agent》系列第 30 章。这个系列基于开源项目 agents-go(OpenAI Agents SDK 的 Go 移植版),用 Go 从零实现一个生产级 Agent 运行时;本章讲「Skills:渐进式披露的 Agent 能力」。
你给 Agent 写了很多"工作方法",怎么写 PR、怎么做 code review、怎么生成 PPT。这些方法如果全塞进 prompt,上下文会爆炸。
Skills 用"渐进式披露"解决,只把索引放进 prompt,让模型按需读全文。这一章拆 agents-go 的
skills包,以及它怎么用 Go 1.24 的os.Root做安全的文件读取。
引子:上下文窗口的预算管理
Agent 的 prompt 有预算,上下文窗口有限(即使百万 token 也很贵)。如果把所有"工作方法"全塞进 instructions:
你的工作方法:
写 PR 的方法:...(500 字)
Code review 的方法:...(800 字)
生成 PPT 的方法:...(600 字)
生成视频的方法:...(700 字)
...还有 20 个方法几万字塞进去,token 爆炸,而且大部分用不上,用户这次只让你写 PR,其他 20 个方法是浪费。
渐进式披露的思路,只放索引,按需读取:
你可以使用以下技能(按需调用 read_skill_file 读取详细说明):
- write_pr: 写 Pull Request
- code_review: 代码审查
- make_ppt: 生成演示文稿
- make_video: 生成视频
...
用户:"帮我写个 PR"
模型:调用 read_skill_file("write_pr/SKILL.md") → 读取详细方法 → 按方法执行只在实际需要时,才把那个 skill 的全文读进上下文。 这就是"渐进式披露",索引常驻,详情按需。

SKILL.md 格式
agents-go 实现的是开放格式 agentskills/agentskills。每个 skill 是一个目录,里面有 SKILL.md:
skills/
├── write_pr/
│ ├── SKILL.md # 技能说明(yaml frontmatter + markdown)
│ ├── template.md # 参考资料
│ └── checklist.md # 检查清单
├── code_review/
│ └── SKILL.md
└── make_ppt/
└── SKILL.mdSKILL.md 的格式(skills.go 的 frontmatter):
---
name: write_pr
description: 写一个规范的 Pull Request
compatibility: 兼容所有支持 function calling 的模型
---
# 写 PR 的方法
## 步骤
1. 理解改动...
2. 写描述...YAML frontmatter(元数据)+ Markdown 正文(详细方法)。 frontmatter 用于生成索引,正文按需读取。
源码对照:Load 和 RenderIndex
Load,扫描 skill 目录
Load(skills.go:68)扫描目录下的 SKILL.md:
// 概念示意
func Load(dir string) ([]Skill, error) {
entries, _ := os.ReadDir(dir)
for _, entry := range entries {
skillFile := filepath.Join(dir, entry.Name(), "SKILL.md")
if !exists(skillFile) { continue } // 没有 SKILL.md 跳过
skill := parseFrontmatter(skillFile) // 解析 yaml
validate(skill) // 名字必须匹配目录名,长度限制
}
}命名规则,skill 的 name 必须匹配目录名(skills.go:126 的 validate)。而且 name 只能是小写字母+数字+单连字符(nameRe,skills.go:38)。严格的命名规则防止路径问题。
RenderIndex,生成发现段
RenderIndex(skills.go:152)生成放进 prompt 的索引:
你可以使用以下技能。需要时调用 read_skill_file 读取详细说明:
- write_pr: 写一个规范的 Pull Request
- code_review: 代码审查
- make_ppt: 生成演示文稿
注意:只在需要时读取相关技能文件,不要一次读取所有,不跨轮携带。这个索引很短(每个 skill 一行),常驻 prompt。详细方法通过 read_skill_file 工具按需读取。
源码对照:ReadFileTool 与 os.Root
ReadFileTool(skills.go:179)是暴露给模型的工具,读 skill 文件:
// skills.go:179(骨架)
func ReadFileTool(root *os.Root) agents.Tool {
return agents.NewFunctionTool("read_skill_file", "读取技能文件", func(ctx, tc, args) (any, error) {
data, err := root.ReadFile(args.Path) // os.Root 限制的读取
if len(data) > maxSkillFileBytes { // 256KiB 封顶
data = data[:maxSkillFileBytes]
}
return string(data), nil
})
}os.Root,目录囚禁
注意 root *os.Root,这是 Go 1.24 的新特性,"rooted I/O"。它把文件操作限制在一个目录内:
root, _ := os.OpenRoot("skills/") // root 只能访问 skills/ 下
root.ReadFile("write_pr/SKILL.md") // ✅ 可以
root.ReadFile("../../etc/passwd") // ❌ 被拒!
root.ReadFile("write_pr/../../../etc/passwd") // ❌ 路径逃逸被防为什么用 os.Root 而不是手动检查路径? 因为手动检查(strings.Contains(path, ".."))容易漏,symlink、URL 编码、Windows 路径分隔符……各种绕过方式。os.Root 在内核层面限制,无法逃逸。
第 31 章详讲 os.Root 的安全性。
256KiB 封顶
const maxSkillFileBytes = 256 * 1024 // 256KiB每个 skill 文件最多读 256KiB。 防止模型读到超大文件(比如有人往 skill 目录放了 100MB 的东西)撑爆上下文。这是防御性编程。
设计权衡:渐进式披露 vs 全塞进 prompt
| 方式 | prompt 占用 | 准确性 | 适合 |
|---|---|---|---|
| 全塞 prompt | 大(几万字) | 高(模型一直看得到) | skill 少(1-3 个) |
| 渐进式披露 | 小(索引几百字) | 中(按需读,可能忘) | skill 多(10+) |
渐进式披露的代价,模型可能"忘记"读 skill。它看到索引,但没主动调用 read_skill_file,就直接凭记忆干活。解法,在 instructions 里强调"执行 X 任务前,必须先 read_skill_file"。
经验法则,skill 少(<5)全塞 prompt;skill 多(>5)用渐进式披露。
小结
读完这一章,你应该理解:
- 渐进式披露,索引常驻 prompt,详情按需读取,省上下文
- SKILL.md 格式,yaml frontmatter + markdown,开放标准
- Load + RenderIndex,扫描目录生成索引
- ReadFileTool,模型调它读 skill 全文
- os.Root 目录囚禁,Go 1.24 新特性,内核级路径限制
- 256KiB 封顶,防超大文件撑爆上下文
Skills 是 Agent 能力的模块化方式,像给 Agent 装"插件"。 下一章深入 os.Root 的安全性。
下一篇
第 31 章,os.Root:Go 1.24 的目录囚禁。为什么传统 path 清洗不够?os.OpenRoot 怎么防 ../ 和 symlink 逃逸?这是 Go 安全编程的新工具。
本文是《从 0 写 Agent》系列第 30 章,案例代码基于开源项目 agents-go(OpenAI Agents SDK 的 Go 移植版)。全系列目录见总览。

