Skip to content

第 13 章 · 两层技能体系与自定义扩展

本章目标:理解 .agents/skills/ 内部技能与 skills/ 公共技能的两层布局差异,学会 SKILL.md 的结构规范,并导览 bin/ 工具带,为编写自定义技能打基础。

13.1 两层布局:为什么技能要分家

打开 FirstMate 仓库,会发现技能存放在两个不同的目录:

text
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/stowskills/stow两个刻意独立的文件,零共享代码。官方维护注释写道:keep them independent——两者各自演进互不拖累。内部版会写 firstmate home 的运营记忆并级联 secondmates;公共版则通过显式指令 → 本地约定 → 私有 .stow-notes.md 兜底的三级规则路由到任意项目。

13.2 metadata.internal: true:对安装器隐身

对比两个 stow 的 frontmatter,能发现关键差异:

yaml
# .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 文件,其结构分三部分:

markdown
---
name: bearings                      # 全局唯一的技能名
description: Generate a concise four-section chat digest...
user-invocable: true                # 是否可被用户直接调用
metadata:
  internal: true                    # 仅内部技能携带
---

<!-- 面向维护者的注释区 -->

# bearings

正文:方法论、步骤、边界约束……
agent 在触发点命中后加载全文遵循执行。

触发点有两类:

  1. 用户调用user-invocable: true):船长敲 /ahoy$stow 等命令;
  2. Agent-only 引用:没有 user-invocable 标记的纯参考技能(如 ask-user-authoritycaptain-hold-lifecyclediagnostic-reasoningharness-adapters 等),由 AGENTS.md 在特定程序节点命名并按需加载。

13.4 为 FirstMate 编写自定义技能的方法论

基于以上结构,为 firstmate 添加自定义技能的思路是清晰的:

text
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 中给出完整清单,这里按职能分组认识最核心的一批:

text
会话生命周期
├── 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. 想写一个任何项目都能用的通用技能,正确的放置位置与要求是?

🛠️ 动手实践

  1. 列出你克隆中 .agents/skills/ 下的全部技能目录,逐个检查 frontmatter:哪些带 metadata.internal: true?哪些有 user-invocable: true?把结果整理成一张两列表格(用户可调用 / agent-only),对照第 12 章验证五个内置技能的位置。
  2. 对比阅读 .agents/skills/stow/SKILL.mdskills/stow/SKILL.md 的正文,找出至少三处因受众不同而产生的行为差异(如写入目标、路由规则),写一段短评说明为什么官方坚持零共享代码。
  3. 挑选三个你在意的 bin/fm-*.sh 脚本(建议含 fm-send.sh),通读各自的头部注释,用自己的话总结每条的使用契约(参数、环境变量要求、失败行为),并与 docs/scripts.md 中的一句话概括互相印证。