第 13 章 · 两层技能体系与自定义扩展
本章目标:理解
.agents/skills/内部技能与skills/公共技能的两层布局差异,学会 SKILL.md 的结构规范,并导览bin/工具带,为编写自定义技能打基础。
13.1 两层布局:为什么技能要分家
打开 FirstMate 仓库,会发现技能存放在两个不同的目录:
firstmate/
├── .agents/skills/ # agent-loaded 内部技能(约 20 个)
│ ├── afk/SKILL.md
│ ├── ahoy/SKILL.md
│ ├── bearings/SKILL.md
│ ├── stow/SKILL.md
│ ├── secondmate-provisioning/SKILL.md
│ └── ...
└── skills/ # 公共、面向安装器的独立技能
└── stow/SKILL.md # 目前只有这一个这个分家不是随意的组织方式,而是由**受众(audience)**决定的:
.agents/skills/:由 firstmate 自己加载。其中每一个都假定「存在一个活的 firstmate home」——离开这个上下文它们就毫无意义,甚至 actively misleading(主动误导)。比如secondmate-provisioning技能引用的路径、工具和词汇只在 firstmate home 里才成立。skills/:公共的、可独立安装的技能,目标是装进任何项目都能用,不依赖 firstmate 的任何私有路径、工具或词汇。
一个有趣的细节是 stow 的「双胞胎」设计:.agents/skills/stow 和 skills/stow 是两个刻意独立的文件,零共享代码。官方维护注释写道:keep them independent——两者各自演进互不拖累。内部版会写 firstmate home 的运营记忆并级联 secondmates;公共版则通过显式指令 → 本地约定 → 私有 .stow-notes.md 兜底的三级规则路由到任意项目。
13.2 metadata.internal: true:对安装器隐身
对比两个 stow 的 frontmatter,能发现关键差异:
# .agents/skills/stow/SKILL.md(内部版)
---
name: stow
description: Sweep the current session for uncaptured durable knowledge...
user-invocable: true
metadata:
internal: true # ← 内部标记
---
# skills/stow/SKILL.md(公共版)
---
name: stow
description: Sweep the current conversation for durable knowledge...
user-invocable: true # ← 没有 metadata.internal
---metadata.internal: true 这个标记的作用是:让 npx skills add 这类安装器在发现(discovery)阶段跳过该技能——内部技能不该被装到别处去。而它对 firstmate 自身的技能加载器完全无感(frontmatter metadata 对 loader 是 inert 的):该加载照常加载,触发点照常触发。
这就是两层体系的安全机制:可见性由受众决定,加载行为由触发点决定。
13.3 SKILL.md 解剖与触发点
每个技能的核心就是一个 SKILL.md 文件,其结构分三部分:
---
name: bearings # 全局唯一的技能名
description: Generate a concise four-section chat digest...
user-invocable: true # 是否可被用户直接调用
metadata:
internal: true # 仅内部技能携带
---
<!-- 面向维护者的注释区 -->
# bearings
正文:方法论、步骤、边界约束……
agent 在触发点命中后加载全文遵循执行。触发点有两类:
- 用户调用(
user-invocable: true):船长敲/ahoy、$stow等命令; - Agent-only 引用:没有 user-invocable 标记的纯参考技能(如
ask-user-authority、captain-hold-lifecycle、diagnostic-reasoning、harness-adapters等),由 AGENTS.md 在特定程序节点命名并按需加载。
13.4 为 FirstMate 编写自定义技能的方法论
基于以上结构,为 firstmate 添加自定义技能的思路是清晰的:
1. 判断受众:
- 依赖 FM_HOME/state/projects 等第一副手语境?
→ 放 .agents/skills/ 并加 metadata.internal: true
- 任何项目都可能用到?
→ 考虑做成 skills/ 下的 standalone 技能
2. 写 SKILL.md:
- name 唯一、description 说清何时触发
- 正文写可执行的方法论而非空洞描述
3. 明确写入边界:
- 内部技能只能通过 FirstMate 既有的所有权和写入边界落盘
4. 测试:仓库自带行为测试运行器 fm-test-run.sh 可验证一条来自官方维护注释的重要纪律:公共技能必须保持 standalone——不含私有项目路径、工具假设或环境分支逻辑。
13.5 bin/ 工具带导览
技能负责「知道怎么做」,bin/ 下的几十个脚本负责「真正去做」。官方在 docs/scripts.md 中给出完整清单,这里按职能分组认识最核心的一批:
会话生命周期
├── fm-session-start.sh # 组合 lock/bootstrap/wake-drain 成单一有序摘要
├── fm-bootstrap.sh # 工具链自检 + 安装已批准的缺失工具
├── fm-teardown.sh # landed-work 完整性测试的所有者(硬规则 3)
└── fm-send.sh # 向任务发消息;FM_HOME 不明确则 fail closed
任务派发与简报
├── fm-spawn.sh # 生成 crewmates/scouts/batches/secondmates
├── fm-brief.sh # 脚手架化 ship/scout/charter 简报(--mode 显式指定)
└── fm-captain-hold.sh # 任务挂起等船长决策、记录答案、把关完成
监督引擎
├── fm-watch.sh # 单例安全的常驻 watcher(第 11 章)
├── fm-turnend-guard.sh # no turn ends blind 判定谓词(第 11 章)
└── fm-guard.sh # 拉取式中途警告
配置与合并
├── fm-config-push.sh # 把声明的继承材料推送到本地/远程 secondmate
├── fm-merge-local.sh # 批准后快进 local-only 项目的本地默认分支
└── fm-project-mode.sh # 从 data/projects.md 解析项目交付姿态两个使用要点值得单独强调:
第一,fm-send.sh 的 fail-closed 设计:如果 FM_HOME 不明确,发送直接失败而不是猜测目标——防止一次 steer 静默地打到另一个 home 上。这是「多 home 隔离」原则在脚本层的体现。
第二,读 header 再用:docs/scripts.md 每行只给一句用途概括,每个脚本的头部注释才是其行为、flag 和契约的权威描述。官方反复强调 read the header before first use。
另外注意 fm-decision-hold.sh 的特殊性:它是一个 one-release 兼容垫片(shim),把已退役的 decision 命令映射到 fm-captain-hold.sh——说明这套工具带本身也在演进,遇到旧命令先查是否有 shim 接管。
本章小结
- 技能分两层:
.agents/skills/是依赖 firstmate home 语境的内部技能;skills/是零依赖、可独立安装的公共技能。 metadata.internal: true让内部技能对 skills.sh 等安装器隐身,同时不影响 firstmate 自身的加载——frontmatter 元数据对 loader 是惰性的。- SKILL.md = frontmatter(name/description/user-invocable/metadata)+ 方法论正文;触发点分用户斜杠调用与 AGENTS.md 命名的 agent-only 加载两类。
- 编写自定义技能先判受众再定位置;公共技能必须 standalone;内部技能必须走既有写入边界。
bin/是执行层工具带:fm-send.sh的 FM_HOME fail-closed 与「读 header 再用」是最重要的两条使用纪律。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. .agents/skills/ 中的技能为什么要携带 metadata.internal: true?
2. 内部版与公共版 stow 技能是什么关系?
3. fm-send.sh 在 FM_HOME 不明确时的行为是?
4. 想写一个任何项目都能用的通用技能,正确的放置位置与要求是?
🛠️ 动手实践
- 列出你克隆中
.agents/skills/下的全部技能目录,逐个检查 frontmatter:哪些带metadata.internal: true?哪些有user-invocable: true?把结果整理成一张两列表格(用户可调用 / agent-only),对照第 12 章验证五个内置技能的位置。 - 对比阅读
.agents/skills/stow/SKILL.md与skills/stow/SKILL.md的正文,找出至少三处因受众不同而产生的行为差异(如写入目标、路由规则),写一段短评说明为什么官方坚持零共享代码。 - 挑选三个你在意的
bin/fm-*.sh脚本(建议含 fm-send.sh),通读各自的头部注释,用自己的话总结每条的使用契约(参数、环境变量要求、失败行为),并与 docs/scripts.md 中的一句话概括互相印证。