第 15 章 · 远程舰队、Relay 与运维工具箱
本章目标:掌握把 secondmate home 整体放到 SSH 可达远程主机的方法,理解 Relay 公开提及应答的安全模型,并学会用 backlog 契约与工具带脚本维持舰队日常运转。
15.1 远程 secondmate:整个 home 搬上另一台机器
第 14 章的 secondmate 与主 home 同机。当船队规模扩大、或需要隔离算力时,可以把整个 secondmate home 放到一台 SSH 可达的远程主机上。官方文档(docs/remote-secondmates.md)定义了完整的 setup、路由、迁移、恢复与安全行为。
前置:SSH 通道的硬性要求
# 1. 在主账号的 ~/.ssh/config 中配置普通 SSH alias
Host fleet-box
HostName 203.0.113.10
User fleet
IdentityFile ~/.ssh/id_ed25519
# 2. 使用普通公钥认证 + 严格主机密钥校验;尽量用专用远程账号
ssh-keygen -t ed25519
ssh-copy-id fleet-box三条安全红线:
- 绝不开启 agent forwarding——
fm-on.sh每次调用都会主动禁用它、转发 setup 与配置的SendEnv模式; - 有界死链检测——
fm-on.sh武装 SSH dead-peer detection,主机重启或断链会在有界窗口内失败,而不是无限挂起; - 远程主机上要自备工具链、所选 worker runtime、所选会话后端与可用凭据。
远程入口与作业 worker
在远程主机上以绝对路径克隆 firstmate,并把固定入口暴露到该账号的非交互 SSH PATH:
# 在远程主机上执行
mkdir -p ~/.local/bin
ln -s /absolute/path/to/firstmate/bin/fm-remote-entrypoint.sh \
~/.local/bin/fm-remote-entrypoint.sh入口点只接受编码后的 argv,且仅限真正可执行的 bin/fm-*.sh 文件——它从不接受 shell 命令字符串。此后每条命令都会校验远程作业 worker、暂存 argv 与 stdin、等待结果、分别回传 stdout/stderr/退出码。
在 macOS 上,这个 worker 是 Aqua 会话里的 LaunchAgent:
~/Library/LaunchAgents/dev.firstmate.remote-job.plist
日志目录: ~/Library/Logs/worker 的行为契约值得记住:一次只跑一个暂存作业;只要队列里来了非长轮询命令,就立刻抢占正在运行的 reply long-poll——交互命令永远不会排在轮询窗口后面。worker 还会自我了断:一旦它的代码根不再是 firstmate 检出(比如是从 worktree 启动的),就自动停止,bin/fm-remote-job-reap-orphans.sh 负责清走这类遗留 worker。
非交互工具发现:不读 shell 启动文件
远程主机上从不运行登录 shell,所以 ~/.profile、~/.bashrc、~/.zshrc 对运行时 PATH 毫无贡献。worker 的 PATH 由文件系统发现构建:
<remote-root>/bin → ~/.local/bin → nvm default bin → asdf/mise shims
→ Nix 目录 → Homebrew 目录 → /usr/bin:/bin:/usr/sbin:/sbin若某个工具只能靠版本管理器的 shell hook 找到,fm-remote-doctor.sh --fix 可以在 ~/.local/bin 生成 firstmate 自有的 wrapper(绝不覆盖非自有文件、绝不装包)。手动写一个也是同样形状:
cat > ~/.local/bin/tasks-axi <<'SH'
#!/usr/bin/env bash
tool_bin="$HOME/.nvm/versions/node/v22.12.0/bin"
PATH="$tool_bin:$PATH"
exec "$tool_bin/tasks-axi" "$@"
SH
chmod +x ~/.local/bin/tasks-axi15.2 供给远程路由与日常启停
先在本地填好普通 secondmate 章程(charter),然后一条命令供给远程路由:
bin/fm-remote-home-seed.sh <id> <ssh-alias> <remote-root> <remote-home> \
myapp=https://github.com/you/myapp.git
# 或不带项目:把最后一个参数换成 --no-projectsseed 过程做了这些事:
1. 在 data/secondmates.md 记录 host: root: home: 三元组
2. 对主机做只读就绪检查;发现缺口自动 --fix,再跑第二次只读检查定论
3. 发送有界 manifest,让远程主机自行克隆它的 firstmate home 与项目 origin
4. 写入 .fm-secondmate-parent 持久记录(标明 parent 路由是 local 还是 remote)几个关键安全语义:
- 项目在远程主机上自行克隆,不从主机器拷贝;每个项目的 origin 必须从远程账号可达;
- 未注册或 local-only 的项目会被拒绝供给——交付模式仍以主机器
data/projects.md为准; - 供给失败会回滚新路由;唯独 SSH 退出码 255 时保留路由,因为远程完成与否未知,必须在同一主机上 reconcile;
- 主 home 侧的持久登记只含路由与
data/<id>下的章程简报,launch 记录要等真正启动才创建。
日常启停与本地 secondmate 用同一条命令:
bin/fm-spawn.sh <id> --secondmate所有远程 secondmate 在同一主机上共享 Herdr 空间 fm-remote,各自保留独立的 2ndmate-<id> workspace。显式要求其他后端会被拒绝——远程主机也会拒绝。主机就绪状态漂移后的启动会直接失败并打印 doctor 的缺口文本,而不是留下半成品端点。
恢复铁律:不可用的远程路由永远不会被降级为本地替换。重启后的 reconcile 会确认已死亡的 secondmate agent 并恢复它们,但对「读不到的远程」保持原样等待,绝不在本地悄悄重建一份冒名顶替。
15.3 Relay:让舰队应答公开提及
Relay(旧称 X mode,标识符仍保留 FMX_/x-/fm-x- 拼写)是可选的公开提及应答集成。它的安全模型非常克制:
1. 默认惰性(ships inert)——不放 token 就没有任何行为变化
2. 在 home 私有的 .env 中放入 FMX_PAIRING_TOKEN 即为选择加入
3. token 是对「公开回复 + 正常可逆生命周期动作」的同意,
不是对破坏性/不可逆/安全敏感动作的授权——那些仍需可信通道确认# .env(gitignored,永不入库)
FMX_PAIRING_TOKEN=<your-pairing-token>工作方式:x-mention <request_id> 或 x-mode-error 唤醒检查到达时,第一副手加载 fmx-respond 技能,由它负责分类、公共安全策略、回复或忽略、任务关联与后续跟进。即使 home 没有舰队工作,也必须保持活跃监督循环,否则提及无法唤醒它。
三条必须刻进脑子的规则:
- 承诺过的最终公开回复是持久状态,不是对话记忆——重启或压缩会话都不能弄丢它,磁盘上的记录负责 reconcile;
- 只有持有 Relay 同意与线程绑定的那个 home 才能发帖——绝不指派 secondmate 或船员去找线程、发回复;
- 上线前可用 dry-run preview:把「将要发出的回复与忽略决定」先记录在本地,确认无误再 go-live。
对每个 Relay 关联的终态结果:有已输入的公开承诺就走 promised-final reconciliation,否则在 teardown 前发出最终完成跟进——7 天内最多三条 public-safe 跟进,只为真实里程碑与最终结果。
15.4 Backlog 契约:持久工作队列
data/backlog.md 是舰队的持久队列,三条契约要记住:
1. 只记工作项,不记智能体——secondmate 永远不出现在主 backlog 里
2. 派给 secondmate 的工作记录在它自己 home 的 backlog
3. 决策就是"留给 captain 的任务":
tasks-axi hold <id> --reason "<reason>" --kind captain [--until <date>]每次派发、完成、决策都要更新 backlog;每次 teardown 与心跳后重新评估排队工作,依赖与时间闸门都清了才派发。后端默认是 tasks-axi(配置在 .tasks.toml);把 config/backlog-backend 写成 manual 可强制退回手改模式,该配置会被 secondmate home 继承。跨 home 交接安全由 secondmate-provisioning 技能与 bin/fm-backlog-handoff.sh 负责。
15.5 运维工具箱与日常巡检
docs/scripts.md 是 bin/ 工具带的权威索引,日常最常用的几个:
| 脚本 | 用途 |
|---|---|
fm-session-start.sh | 会话启动摘要(第 2 章) |
fm-bearings-board.sh / fm-bearings-snapshot.sh | /bearings 的看板与快照支撑 |
fm-spawn.sh | 启动 crewmate / secondmate(fail-closed 校验) |
fm-crew-state.sh | 深读某个任务的真实当前状态 |
fm-backlog-handoff.sh / fm-backlog-receive.sh | 跨 home backlog 交接 |
fm-remote-home-seed.sh / fm-on.sh | 远程路由供给与远程命令执行 |
fm-teardown.sh | 带 landed-work 测试的安全收尾 |
fm-tool-update-check.sh | 依赖工具的更新检查(清单在 config/watched-tools.json) |
日常巡检的推荐节奏:
# 每天开工:一条命令看全舰队四段式摘要(第 12 章)
/bearings include PRs
# 疑似某任务卡住:深读单个任务的当前状态
bin/fm-crew-state.sh <task-id>
# 收工前:知识清扫 + 记忆归档(第 12 章)
/stow15.6 本章小结
- 远程 secondmate = 整个 home 放上 SSH 主机:公钥认证、禁 agent forwarding、有界死链检测是三条红线;
- 远程入口只接受编码 argv 的
fm-*.sh,作业 worker 单作业串行、可抢占长轮询、随检出失效自我了断; - PATH 靠文件系统发现构建,shell 启动文件不参与;缺口由 doctor
--fix的自有 wrapper 补齐; - 供给用
fm-remote-home-seed.sh,就绪检查「只读 → --fix → 再只读」,失败回滚(SSH 255 除外);启动用fm-spawn.sh <id> --secondmate,远程统一 Herdrfm-remote空间; - 恢复铁律:远程路由不可用绝不降级为本地替换;
- Relay 默认惰性,token 即同意范围;承诺的公开回复是磁盘持久状态,只有绑定 home 能发帖,dry-run 先行;
- backlog 只记工作项;决策是
--kind captain的 hold;日常巡检靠/bearings、fm-crew-state.sh与/stow。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. 关于远程主机的 SSH 通道,下列哪项是 FirstMate 明确禁止的?
2. fm-remote-entrypoint.sh 接受什么样的输入?
3. 远程路由供给过程中 SSH 返回退出码 255 时,系统会怎么做?
4. 关于 Relay 的 promised final public reply,正确的理解是?
🛠️ 动手实践
- 用一台云主机或家里闲置的 Mac/Linux 机器,完整走一遍 15.1–15.2 节:配置 SSH alias、克隆 firstmate、暴露 entrypoint、
fm-remote-home-seed.sh供给一个不带项目的远程路由,并截图记录 doctor 就绪检查的「红 → --fix → 绿」过程。 - 演练恢复铁律:启动远程 secondmate 后,在远程主机上
kill掉其会话进程,观察主 home 重启后的 reconcile 行为;再临时断开 SSH(改错端口),确认主 home 报告「远程不可用」而不是在本地悄悄重建。 - 为你的 home 配置 Relay:在
.env放入 pairing token,先用 dry-run preview 模式跑几天,检查本地记录的 would-be replies 内容是否都符合「可公开、可逆」标准,再决定是否 go-live;写一份你自己的公开回复安全清单。