第 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 更新发行版,而不会碰任何一个实例的舰队记录。
# 未设置:仓库根目录就是家
cd firstmate && claude
# 设置后:状态全部落在 ~/fm-homes/main,代码仍在原地
FM_HOME=~/fm-homes/main claude此外还有两个配套变量:FM_ROOT_OVERRIDE 可以替换脚本使用的 firstmate 仓库根目录;FM_STATE_OVERRIDE、FM_DATA_OVERRIDE、FM_PROJECTS_OVERRIDE、FM_CONFIG_OVERRIDE 则可以单独覆盖某一个运营目录——主要用于测试和特殊 harness 环境。
5.2 四大私有目录的职责分工
configuration.md 是顶层布局的唯一权威定义。四个目录各司其职:
$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 提交与更新:
AGENTS.md / README.md / CONTRIBUTING.md 运营契约与文档
.tasks.toml 默认 backlog 后端配置
.github/workflows/ 共享 CI 与 PR 强制
bin/ 助手脚本工具带
.agents/skills/ firstmate 内部技能
skills/ 面向公共安装者的独立技能而以下路径属于船长私有的 gitignored 材料,永远不会进入版本控制:
.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 里。
# 正确:显式指定 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.sh、fm-spawn.sh、fm-afk-launch.sh 在持久化路径或把它传给其他进程之前,会把相对形式的 FM_HOME 和 override 目录解析为绝对路径;无法解析的相对目录会被拒绝,并在报错中点名是哪个变量出了问题。
5.5 实操:搭建并验证一个独立的 home
下面用最小步骤创建一个脱离仓库根目录的运营之家,并确认目录骨架:
# 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 即可,互不干扰:
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 材料?
🛠️ 动手实践
- 分别在
FM_HOME未设置和设置为~/fm-homes/lab两种情况下各启动一次 firstmate 并派发一个小任务,对比两种布局下data/、state/出现的位置,画出你机器上的实际目录树。 - 故意在未设置
FM_HOME的 shell 里调用bin/fm-send.sh <id> 'ping',观察它的失败行为;再设置FM_HOME后重试,验证「fails closed」语义。 - 为「personal」和「work」两个场景分别创建独立 home,各派发一个不同项目的任务,然后用
ls检查两个 home 的projects/内容,确认克隆彼此隔离。