Skip to content

第 5 章 · 运行时布局:FM_HOME 与目录约定

本章目标:掌握 FM_HOME 机制与 data/state/config/projects/ 四大私有目录的职责分工,理解 tracked 共享材料与 gitignored 私有材料的边界,学会安全地运行多个 firstmate 实例。

5.1 一个实例一个家:FM_HOME 是什么

FirstMate 的代码仓库本身是「发行版(distro)」,但每个运行中的 firstmate 实例都需要自己的**运营之家(operational home)**来存放私有状态。FM_HOME 环境变量就是这个家的地址:

  • 未设置时:大多数脚本直接把仓库根目录当作 home;
  • 设置后:脚本仍然从本仓库的 bin/ 运行,但 state/data/config/projects/ 全部改从 $FM_HOME 读取。

这个设计让「代码」和「状态」彻底分离:你可以随时 git pull 更新发行版,而不会碰任何一个实例的舰队记录。

sh
# 未设置:仓库根目录就是家
cd firstmate && claude

# 设置后:状态全部落在 ~/fm-homes/main,代码仍在原地
FM_HOME=~/fm-homes/main claude

此外还有两个配套变量:FM_ROOT_OVERRIDE 可以替换脚本使用的 firstmate 仓库根目录;FM_STATE_OVERRIDEFM_DATA_OVERRIDEFM_PROJECTS_OVERRIDEFM_CONFIG_OVERRIDE 则可以单独覆盖某一个运营目录——主要用于测试和特殊 harness 环境。

5.2 四大私有目录的职责分工

configuration.md 是顶层布局的唯一权威定义。四个目录各司其职:

text
$FM_HOME/
├── data/       持久舰队档案:项目与 secondmate 注册表、船长偏好、learnings、
│               backlog、任务简报、scout 报告
├── state/      运行时记录:任务元数据、append-only 状态事件、endpoint 信号、
│               watcher 与唤醒队列协调、terminal-outcomes 回执、away-mode 状态、
│               Relay 生成的工件、secondmate 待回复记录等
├── config/     本地运营选择(gitignored):后端选择、harness 覆盖等
└── projects/   本地项目克隆;firstmate 只读,
                改动只通过硬规则一的受守卫例外发生

记忆方法是按生命周期分层data/ 是要长期保留的「档案室」,state/ 是随时在变的「值班日志」,config/ 是你的「操作偏好面板」,projects/ 则是借来阅读的「项目书架」。

scout 任务的产出就落在 data 下,例如某次侦察的报告位于 data/<id>/report.md——这解释了为什么 scout 不产 PR 却依然可追溯。

5.3 tracked 共享材料 vs gitignored 私有材料

AGENTS.md 把仓库内容划成泾渭分明的两类。共享 tracked 材料是发行版的一部分,随 git 提交与更新:

text
AGENTS.md / README.md / CONTRIBUTING.md   运营契约与文档
.tasks.toml                               默认 backlog 后端配置
.github/workflows/                        共享 CI 与 PR 强制
bin/                                      助手脚本工具带
.agents/skills/                           firstmate 内部技能
skills/                                   面向公共安装者的独立技能

而以下路径属于船长私有的 gitignored 材料,永远不会进入版本控制:

text
.env          可选的 Relay 配对 token(LOCAL)
data/         持久舰队档案
state/        运行时记录
config/       本地运营选择
projects/     本地项目克隆
.no-mistakes/ no-mistakes 流水线本地数据

firstmate 对这两类的写权限也不同:它可以维护本仓库的私有运营状态;对共享 tracked 材料的修改要走本仓库自己的 no-mistakes 流水线和 PR 路径——它对自己也要遵守同样的合并权威

5.4 fails closed:fm-send.sh 的显式 FM_HOME 要求

多实例并存的场景里最大的风险是「指令发错了家」。为此,bin/fm-send.sh 刻意比一般脚本更严格:它要求 FM_HOME 必须显式设置才会解析目标,否则直接失败(fails closed),绝不静默回退到仓库根目录——这样一次 steer 永远不会悄悄落到错误的 home 里。

sh
# 正确:显式指定 home 再发 steer
FM_HOME=~/fm-homes/main bin/fm-send.sh <id> '请补充回归测试'

# 错误示范:未设置 FM_HOME,fm-send.sh 会拒绝执行而不是猜测
bin/fm-send.sh <id> 'hello'    # fails closed

另一个安全细节:fm-brief.shfm-spawn.shfm-afk-launch.sh 在持久化路径或把它传给其他进程之前,会把相对形式的 FM_HOME 和 override 目录解析为绝对路径;无法解析的相对目录会被拒绝,并在报错中点名是哪个变量出了问题。

5.5 实操:搭建并验证一个独立的 home

下面用最小步骤创建一个脱离仓库根目录的运营之家,并确认目录骨架:

sh
# 1. 创建家目录(首次启动后各子目录会按需生成)
mkdir -p ~/fm-homes/main

# 2. 从 firstmate 仓库根目录启动 primary harness,并指向该 home
cd /path/to/firstmate
FM_HOME=~/fm-homes/main claude

# 3. 让 firstmate 执行一个小任务后,检查家目录里出现了什么
ls ~/fm-homes/main
# data/  state/  config/  projects/

如果你需要多个并行实例(比如一个私人项目、一个工作项目),给它们各自独立的 FM_HOME 即可,互不干扰:

sh
FM_HOME=~/fm-homes/personal claude   # 终端 A
FM_HOME=~/fm-homes/work claude       # 终端 B

注意:每个 secondmate 也拥有自己持久隔离的 FM_HOME(含独立的 state、backlog、projects 和 session lock)——这是第 14 章 Secondmate 机制的物理基础。

本章小结

  • FM_HOME 选择一个 firstmate 实例的运营之家;未设置时使用仓库根目录,设置后 state/data/config/projects 全部来自 $FM_HOME
  • 四大目录按生命周期分工:data/ 持久档案、state/ 运行时记录、config/ 本地偏好、projects/ 只读的项目克隆;
  • 共享 tracked 材料随发行版走 git,.env/data/state/config/projects/.no-mistakes/ 属于船长私有的 gitignored 材料;
  • bin/fm-send.sh 要求显式 FM_HOME 否则失败关闭,杜绝 steer 发错家;相对路径在持久化前必须能解析为绝对路径;
  • 多实例 = 多个独立 FM_HOME,secondmate 同样以独立 home 实现隔离。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. 当 FM_HOME 被显式设置后,脚本的运行方式是?

2. 下列哪个内容属于 data/ 目录而不是 state/ 目录?

3. 为什么 bin/fm-send.sh 要求显式设置 FM_HOME?

4. 下列哪一组全部属于船长私有的 gitignored 材料?

🛠️ 动手实践

  1. 分别在 FM_HOME 未设置和设置为 ~/fm-homes/lab 两种情况下各启动一次 firstmate 并派发一个小任务,对比两种布局下 data/state/ 出现的位置,画出你机器上的实际目录树。
  2. 故意在未设置 FM_HOME 的 shell 里调用 bin/fm-send.sh <id> 'ping',观察它的失败行为;再设置 FM_HOME 后重试,验证「fails closed」语义。
  3. 为「personal」和「work」两个场景分别创建独立 home,各派发一个不同项目的任务,然后用 ls 检查两个 home 的 projects/ 内容,确认克隆彼此隔离。