Skip to content

第 18 章 · 实战三:Secondmate 远程舰队规模化运营

本章目标:把第 16 章搭起的个人舰队升级为「本地 secondmate + 远程 home + Relay 应答 + 离开模式值守」的规模化编制,并完成一轮生产级运维演练。

18.1 实战蓝图与准备

前两章实战中,你始终只指挥一支由 firstmate 直接管理的 crew。本章引入两个编制升级:

text
升级前(实战一/二):
captain ──> firstmate ──> crewmate × N(每任务一个 worktree)

升级后(本章):
captain ──> firstmate(primary home)
             ├──> secondmate A(本地隔离 FM_HOME)
             ├──> secondmate B(远程主机上的完整 home,Herdr fm-remote 会话)
             └──> crewmate × N

回顾官方定义:secondmate 是一个拥有独立 firstmate home 和章程(charter)的 crewmate,而不是第二套架构。它有自己的持久 FM_HOME——独立的 state、backlog、projects 和 session lock;并且 secondmate 不再孵化 secondmate(secondmates do not spawn secondmates),所以 config/secondmate-harness 只在主 home 生效、不会被继承。

本章演练需要准备:

  • 已完成实战二的舰队环境(tmux 后端可用,gh auth login 完成);
  • 一台 SSH 可达的远程主机(云服务器或家里另一台 Mac/Linux 均可),配置好公钥认证;
  • 一个 X 或 Discord 账号(Relay 演练用,可跳过)。

18.2 第一步:Provision 本地 Secondmate

先给 secondmate 写章程,再启动。章程就是它的 brief(data/<id>/brief.mdkind=secondmate 的那份):告诉这位"第二副手"它负责哪类任务、向谁汇报。

对 firstmate 下达自然语言指令即可完成 provision,也可以直接用脚本启动:

sh
# 在 firstmate 主 home 的仓库根目录执行:
# 让 firstmate 为 id 为 "ops" 的 secondmate 建 home 并启动
bin/fm-spawn.sh ops --secondmate

launch 时 primary 解析 secondmate 专用 harness。指定方式是本地 gitignored 文件 config/secondmate-harness,首行格式为 <harness> [<model>] [<effort>]

text
# config/secondmate-harness —— 给第二副手固定用 pi,模型与努力档位可选
pi

解析规则要记牢:

  • 该文件缺省或写 default 时,回退到 config/crew-harness,再回退到 primary 自己的 harness;
  • 显式传给 fm-spawn.sh 的 harness 参数或 --model / --effort 优先于该文件,但仅对那一次 spawn 生效;
  • 该文件不被继承——因为 secondmate 不会再启动 secondmate,这个开关天然只在 primary 手里。

18.3 第二步:验证隔离 FM_HOME 与 Session Lock

启动后,验证这位副手确实住在自己的房子里,而不是和 primary 共用一套状态:

sh
# 查看 secondmate 路由表:host/root/home 三元组登记在案
cat data/secondmates.md

# 校验路由记录(本地与远程形式都支持)
bin/fm-home-seed.sh validate

确认以下隔离事实:

检查项期望结果
FM_HOMEsecondmate 有自己的 data/、state/、config/、projects/
session lock它的 home 有自己的会话锁,不会与 primary 抢锁
继承材料data/captain-shared.md 以只读方式到达 secondmate,所有权仍在 primary
孵化权限secondmate 内没有 config/secondmate-harness 的继承副本

config/startup-memory-budget 是个例外样例:它是 primary-authoritative 的 per-home 预算,默认物化为 7500 估算 token,通过继承契约下发到每个 secondmate home——但每个 home 各自单独核算,不做舰队总量合并。可以用工具核对:

sh
bin/fm-startup-memory-budget.sh read     # 校验并打印生效值
bin/fm-startup-memory-budget.sh report   # 对三个记忆文件做核算

18.4 第三步:迁移为远程 Secondmate(Doctor → Seed → Spawn)

现在把一位 secondmate 的整个 home 搬到远程主机。三条铁律先记住:

  1. 远程的是整个 home,不是单个 worker;primary 仍拥有路由与监督权,远程 home 拥有自己的 projects、backlog 和 workers;
  2. 远程 secondmate agent 永远跑在 Herdr 后端的共享 fm-remote 会话里——该服务属于远程主机自己的 GUI 登录会话而非 SSH 连接,所以断线不会带走 agent 端点;
  3. 不可用的远程路线只会被标记为 unknown,绝不降级为本地替换

3.1 远程主机预埋

在远程主机上克隆 Firstmate 到绝对路径,并把固定入口暴露到非交互 PATH:

sh
# 在远程主机上执行
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 命令字符串。注意不要启用 agent forwarding——fm-on.sh 每次调用都会主动禁用它,并武装有界的 SSH dead-peer 检测,让消失的主机在有限时间内失败而不是无限挂起。

3.2 就绪检查与修复

bin/fm-remote-doctor.sh 是"什么叫做准备好接收远程 secondmate"的唯一权威:

sh
# 只读体检:打印实际 PATH、逐项报告工具解析位置与就绪检查结果
bin/fm-on.sh <ssh-alias> fm-remote-doctor.sh

# 自动修复可自动化缺口(安全可重跑),修完再跑一次只读检查裁决
bin/fm-on.sh <ssh-alias> fm-remote-doctor.sh --fix

必需工具集是:gitjqherdr、兼容的 tasks-axitreehouse,外加至少一个 agent harness(claude / codex / opencode / pi / pi-signed / grok / kimi)。缺口分两类标注:fixable: 可被 --fix 关闭;human: 必须有人到那台机器上操作——首次控制台登录、FileVault 解锁、安装无法用安全 wrapper 解析的工具等,这些永远不会被静默自动尝试。

3.3 播种远程 Home

先把章程建好(与本地 secondmate 相同),然后运行 seed。五段式参数一目了然:

sh
bin/fm-remote-home-seed.sh ops mac-studio \
    /srv/firstmate /home/deploy/fleet-home \
    webapp=https://github.com/you/webapp.git docs

参数依次为:<id><ssh-alias><remote-root>(远程代码克隆)、<remote-home>(持久 home,不得与代码根重叠)、项目清单。项目可以带显式 origin URL,裸项目名则要求本机已有对应 clone 可读取其 origin。seed 会把 host:root:home: 记入 data/secondmates.md,按"只读检查 → 需要时 --fix → 再次只读检查裁决"的顺序把关;主机持续红灯则打印剩余缺口、回滚注册、不在远程创建任何东西。项目树不会被复制,origin URL 由远程主机自行验证后克隆。

3.4 启动与日常指令

启动/恢复远程 secondmate 用与本地完全相同的命令;发指令也走同一条路:

sh
# 启动或恢复(内部跑同一道就绪门,恢复不走弱化通道)
bin/fm-spawn.sh ops --secondmate

# 发送路由请求:落盘为远程 home steering inbox 的持久记录 + 尽力而为的门铃
FM_HOME=<primary-home> bin/fm-send.sh fm-ops '盘点 docs 仓库的未关闭 issue'

同一主机上的所有远程 secondmate 共享 fm-remote 会话,各自保留独立的 2ndmate-<id> workspace。请求其他后端会被拒绝——本机与远程都拒绝。

18.5 第四步:Recovery 演练与「永不降级」

这是本章最重要的安全课。人为制造故障,观察系统的自愈边界:

sh
# 在远程主机上直接杀掉 Herdr fm-remote 里的 secondmate 进程,
# 或者干脆重启那台机器(GUI 自动登录已配置的前提下)

然后回到 primary,重新执行 bin/fm-spawn.sh ops --secondmate。你会看到 startup liveness recovery 通过同一道就绪门重新拉起死掉的端点——恢复路径不弱于首次创建路径。

再演练网络故障:拔掉到远程主机的链路(或停掉 sshd),随后观察 primary 的行为。此时:

  • fm-send.sh 遇到 SSH exit 255 表示传输失败或远程完成状态未知;它会以保序方式重试一次 correlation-preserving 的投递腿;
  • 若仍无法确认,只有 fm-send 打印的那条带 FM_PENDING_REPLY_EXISTING_CORR=<id> 的重发命令才是安全的——普通重跑会铸造新的 correlation,不具备幂等性;
  • 远程读取不可达时结果是 unknown 而不是死亡证明fm-peek.shfm-crew-state.sh 会把读取路由到远端主机,读不到不代表端点已死;
  • 最关键的:primary 会把不可用的远程 home 投影为 unknown,绝不会悄悄起一个本地 secondmate 顶替它。这条"never turns an unavailable remote route into a local replacement"的原则防止了数据分叉与职责混乱。

18.6 第五步:接入 Relay(Dry-Run 先行)

Relay 让同一支舰队应答你在 X 和 Discord 上的公开提及。开启只需一个本地 .env pairing token,且不改变非 Relay 行为

能力边界(官方 README 口径):正常可逆的 mention 请求走与聊天相同的生命周期;firstmate 可以确认 spawned 工作;七天内最多发布三条 public-safe 的完成跟进。线程里承诺过的最终回复会成为持久状态、从磁盘 reconcile——重启或对话压缩都不会丢。

上线流程务必先 dry-run:

text
第一步:在本地 home 放置 .env pairing token(gitignored,不入库)
第二步:dry-run preview —— 系统在本地记录"将要发出的回复与将要执行的忽略",
        但不真的发布任何内容
第三步:人工审阅 preview 记录,确认措辞与边界符合预期
第四步:go-live,让真实 mention 流量进来

dry-run 的价值在于把"对外副作用"变成可审阅的本地记录——这与 hard rule 2(合并需 captain 明确同意)是同一种哲学:对外不可逆动作永远需要人过目

18.7 第六步:/afk 离开模式与 Wedge-Alarm

下班了?输入 /afk 进入离开模式监督:

  • 子监督者(bin/fm-supervise-daemon.sh)在 bash 里自处理例行通知,零 token 消耗;
  • captain 相关事件与有界的外部等待复查被打包成批量 digest 升级给你;
  • 若交付卡住,主动发出响亮告警。

告警的兜底是 wedge-alarm:当注入器无法在 FM_MAX_DEFER_SECS 内确认一次提交成功,就触发限频的响亮警报。配置文件 config/wedge-alarm 每行一条频道指令:

text
# config/wedge-alarm —— 每行一条;非 off 频道均尽力触发
osascript              # macOS 通知中心横幅(终端窗格之外)
command:curl -s https://pager.example.com/alert -d "$1"   # 自定义命令,摘要经 $1 与 stdin 传入

缺省行为是 auto:macOS 上解析为 osascript(通知中心),其他平台无内建 OS 频道、建议配置 command:。要点:

  • 告警只在真实的 max-defer 卡死后触发,且每个 max-defer 窗口最多一次(限频);
  • 每次调用受 FM_WEDGE_ALARM_TIMEOUT_SECS 约束(默认 10 秒),超时会终止通知进程组并轮到下一频道;
  • 单频道失败只记警告并继续下一频道,守护循环不会崩。

次日回岗,一句 /ahoy 即可获得自上一条真实消息以来的事件回顾,未决事项按影响排序逐条引导你决策;如果 /ahoy 是本次会话第一条真实消息,它会退回到 Bearings 摘要。

18.8 第七步:/stow 清扫归档与全家桶更新

规模化运营的最后一块拼图是知识治理。会话结束前运行:

text
/stow

这条内置技能会:清扫会话中未捕获的持久知识、把本会话已知未归档或已失效的工作记录落盘、以衰减与冷归档策略策展分层 startup memory、执行每个 home 的预算约束(超出则上报决策而非硬塞)、级联同步到已注册的 secondmates。预算即 18.3 节的 config/startup-memory-budget,稳定估算公式为每文件 ceil(UTF-8 字节数 / 3)——保守的可移植近似,而非某个厂商的精确 tokenizer。

最后做全舰队更新:

text
/updatefirstmate

该技能对 running firstmate 及其所有 secondmates 执行 fast-forward-only 的 origin 拉取更新,然后重读指令并向 secondmates 发出提示。fast-forward-only 保证更新永远不会制造历史分叉——拉不动就停下来报告,而不是强推。

18.9 生产 Checklist

把本章演练固化为周期性运维节奏,逐项自查:

text
□ 安全边界:hard rule 1~5 复述无误;projects/ 对 firstmate 保持只读;
  合并权限未被 yolo 化的项目仍需明确批准
□ 密钥卫生:.env(Relay token)确认 gitignored;config/ 下各 LOCAL 文件未入库;
  远程主机无 agent forwarding
□ 监控节奏:wedge-alarm 频道可达(手动触发一次验证);/afk digest 正常送达;
  fm-tool-update-check.sh 的 watched-tools.json 与实际依赖一致
□ 灾备演练:杀远程会话后 fm-spawn.sh 能恢复;SSH 断链期间 primary 显示
  unknown 且未降级本地;恢复后 pending reply 经 correlation-preserving 重发补齐
□ 知识治理:/stow 定期清扫;startup memory 核算不超预算;
  data/learnings.md 与 captain.md 内容仍然准确
□ 版本管理:/updatefirstmate 全家桶 fast-forward-only 更新成功;
  远程 doctor 只读检查绿灯

本章小结

  • secondmate = 独立 FM_HOME + 章程的 crewmate:自有 state/backlog/projects/session lock,且不再孵化 secondmate,因此 config/secondmate-harness 只在 primary 生效;
  • 远程化迁移三步走:fm-remote-doctor.sh 就绪把关(human 缺口永不全自动)→ fm-remote-home-seed.sh 播种 → fm-spawn.sh <id> --secondmate 启动,全程 Herdr fm-remote 会话;
  • 故障语义必须精确:SSH exit 255 是"未知"不是"死亡",不可用的远程路线投影为 unknown 且永不降级为本地替换;唯一安全的重发是 correlation-preserving 命令;
  • Relay 上线先 dry-run:would-be replies 本地留痕、人工审阅后再放行,七天内最多三条公开跟进;
  • /afk + wedge-alarm 构成无人值守兜底:bash 子监督者零 token 处理例行事件,卡死时限频响亮告警;/stow/updatefirstmate 收尾知识治理与版本推进。

🧪 随堂测验

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

1. 关于 config/secondmate-harness,下列说法正确的是?

2. 向远程 secondmate 发送指令后遇到 SSH exit 255,正确的理解与做法是?

3. 远程 secondmate 的 agent 运行在哪个会话中?为什么?

4. 关于 wedge-alarm 与 /afk 的组合,下列描述错误的是?

🛠️ 动手实践

  1. 为你的舰队添加一个本地 secondmate:撰写一份限定范围的章程(例如只负责代码审查类 scout 任务),通过 config/secondmate-harness 固定它与 primary 不同的 harness,启动后用 data/secondmates.mdbin/fm-home-seed.sh validate 验证隔离性,并向它发送一条测试指令确认回复链路。
  2. 选一台 SSH 可达主机完成远程化:先跑只读 doctor 记录全部缺口,分类哪些是 fixable、哪些是 human;完成修复与人工步骤后 seed 一个带单个项目的远程 home,最后故意中断 SSH 链路,观察 primary 是否将路线显示为 unknown 且不产生任何本地替代。
  3. 设计你的无人值守夜班:配置 config/wedge-alarm 使用 command: 频道把告警推送到手机,开启 /afk 后人为制造一次注入卡顿场景验证限频告警触发;次日早晨用 /ahoy 回顾批量 digest,再用 /stow 完成知识清扫并核对 startup memory 未超预算,全程记录成你团队自己的值班手册。