firstmate重启免疫设计剖析:状态落盘机制如何保证在途工作永不丢失
【免费下载链接】firstmateTalk to one agent. Ship with a crew.项目地址: https://gitcode.com/gh_mirrors/fi/firstmate
firstmate 是一个"agent 发行版",让你只和一个 AI 大副对话,就能指挥一队 AI 编码 agent 并行干活。它最硬核的设计是重启免疫:所有关键状态都写入磁盘而非聊天记忆,杀掉任何会话后,下一个会话都会从磁盘自动对账、原地续跑。本文剖析这套状态落盘机制的三层结构与恢复流程,帮你看懂它为何敢承诺"在途工作永不丢失"。
设计哲学:为什么重启应该是个"无事发生"
大多数 AI 编码工具有个通病:上下文只活在对话里。会话一关,进行到哪一步、答应了什么、卡在哪里,全部清零。
firstmate 在 VISION.md 里把这件事上升为一条核心原则——A restart is a non-event(重启是非事件):
一切重要的东西都能活过任何对话的死亡:在途的工作、做出的承诺、待定的决策、船长的偏好,都存在于持久化记录中,而不是聊天记忆里。
这句话翻译过来就是:对话可以随时死,磁盘上的记录才是权威。系统通过"从磁盘 + 活跃会话状态对账"恢复,所以杀任何会话都"不会丢东西、不会吓到任何人"。
这个承诺能成立,靠的是下面三层落盘结构。🧭
第一层:data/目录——持久任务记录
data/是整个"船队"的账本,全部是本地 Markdown 文件,重启后依然完整:
| 文件 | 作用 |
|---|---|
| data/backlog.md | 任务队列、依赖关系、历史(持久的"待办账本") |
data/captain.md | 本 home 的船长偏好与工作方式 |
data/captain-shared.md | 主 home 共享给二副的偏好 |
data/learnings.md | 运行中踩过的坑、经验教训(带日期、会衰减整理) |
data/projects.md | 项目注册表与交付姿态 |
data/<id>/brief.md | 每个任务的派工简报 |
data/<id>/report.md | 侦察类任务的调研报告,拆除后依然保留 |
关键点在于:任务的"是什么、做到哪、怎么交付"这些信息从来不在对话里,而在这些文件里。worker 死了可以重拉,但账本本身永远在。
第二层:state/目录——运行时信号与持久唤醒队列
state/存放运行时记录与信号(详见 AGENTS.md 的目录约定),它是"进程死了、记录还在"的缓冲层:
state/<id>.status—— worker 追加的唤醒事件日志(注意:是事件历史,不是当前状态);state/<id>.meta—— 任务元数据(PR 编号、worktree、harness 等);state/<id>.inbox/—— 持久的指令收件箱,发给 worker 的每条消息都是落盘记录,worker 处理完才移走;state/.wake-queue——持久唤醒队列,这是重启免疫的心脏。
为什么唤醒队列如此重要?看 docs/architecture.md 的设计:可执行的唤醒只在恢复证据发布之后才写入state/.wake-queue。这意味着哪怕 watcher 进程被中途打断、或者处理回合崩了,队列记录依然完好——下一个会话启动时会原样呈现这些待办唤醒,并且有"生成期绑定确认"机制:处理完之前中断,工作就保持持久状态,等幂等的重放处理,绝不丢。
一句话:通知不是"发消息",而是"写记录 + 响门铃"。门铃没听见不要紧,记录还在磁盘上等着。📮
第三层:会话后端——活着的 worker 端点
前两层是"纸面记录",真正干活的 worker 则活在会话后端里(tmux 为硬默认,另有 herdr、zellij、Orca、cmux)。后端为每个任务提供一个可见的独立端点(tmux 窗口、herdr 标签页等),worker 在自己的 git worktree 里干活。
重启免疫在这里的体现是:firstmate 重启时,不会盲目重建一切,而是探测每个已记录端点的存活状态,只处理"确认死亡"的端点——二副(secondmate)agent 确认死亡后会走同一派工路径自动重拉,而存活状态模糊的端点一律不动,避免重复监督。宁可慢,不可错。
会话启动对账:7 步恢复清单
每次新会话启动,bin/fm-session-start.sh 会执行一条固定的对账流水线(定义在 AGENTS.md 第 3 节):
- 锁——先拿到 home 级会话锁,保证同一时刻只有一个会话改状态;
- 引导——工具链检测、worktree 纠缠检查、二副存活清扫(死二副在此自动重拉);
- 唤醒队列——把
state/.wake-queue里的持久唤醒作为本轮第一个工作队列呈现,同时打印全局 OPEN DECISIONS(未决决策)与 UNREAD STATUS(未读状态); - 监督操作说明——按当前 harness 渲染监督协议块;
- 舰队状态摘要——每个任务的元数据、状态日志尾部、端点存活快读;
- 网络检查——GitHub 鉴权、死二副重拉、项目克隆刷新,全部在后台延迟阶段跑;
- 上下文摘要——
projects.md、secondmates.md、captain.md、learnings.md全文载入。
这套流程保证了 docs/architecture.md 中Restart-proof章节承诺的体验:杀了会话重开,firstmate 自己读账本、对现状、补唤醒,然后"接着干"。
/stow技能:收尾前先让记忆落盘
还有一个容易被忽略的细节:对话里往往藏着还没写进磁盘的持久知识——临时说出口的偏好、刚做下的决定、没记下来的下一步。
firstmate 提供了 /stow 技能来解决这个缝隙:
- 扫一遍当前会话,找出所有"只存在于聊天里"的持久知识;
- 按固定优先级路由到磁盘:船长按钮偏好 →
captain.md,项目事实 → 项目的AGENTS.md,未完成的下一步 → backlog; - 先检查再更新,绝不盲目追加;条目分层(pinned / aging / perishable)并会随时间衰减归档;
- 最后给出一句诚实的裁决:"现在这个会话是否安全可以结束/重置",并附一个可复制的"恢复指针",告诉新会话该先加载哪些文件。
docs/verification/stow-memory.md 中记录了这套 JIT 加载本地记忆的验证证据——新会话确实能从一个被 git 排除的本地技能目录里把知识捞回来。这正是"重启免疫"的最后一块拼图:不仅系统状态落盘,连你的对话记忆也有落盘出口。
worker 死了怎么办:三条恢复路径
重启免疫不等于"不用处理故障",AGENTS.md 第 5 节定义了明确的恢复路径:
| 故障 | 恢复动作 |
|---|---|
| 二副确认死亡 | 会话启动时自动重拉,不碰其子树 |
| 普通 worker 端点死亡 | 走 stuck-crewmate-recovery:保住已记录的 worktree 与未落地的工作,只恢复所有权 |
| 工作未落地就要清理 | teardown 拒绝拆除——"拒绝丢弃"是发现项,不是障碍 |
原则非常一致:恢复只对齐已记录的直接报告,永远不清扫、不猜测、不发明工作。
总结:这套设计值得借鉴的 5 个点
- 记录即权威——对话是易失缓存,磁盘才是数据库;
- 事件与状态分离——状态日志是追加式事件流,当前状态要靠专门的对账脚本读,避免"读最后一行"的陷阱;
- 先落盘后通知——队列记录先于通知存在,通知丢了记录还在;
- 幂等恢复——中断前的工作保持可重放,重复处理不会造成重复结果;
- 保守恢复——模糊的存活信号一律不动手,确认死亡才重建。
对新手用户而言,最直观的体验是:你可以随时杀掉整个 tmux、重启电脑、甚至换台机器 clone 一份 home——在途任务、未决决策、已承诺的回复,都会在下一个会话里自己"回来上班"。这也就是 README 里那句 "Restart-proof" 的全部含义。
想深入了解?建议按顺序读 README.md 的 How It Works、docs/architecture.md 的 Restart-proof 章节,以及 AGENTS.md 的目录约定与 Recovery 章节。
【免费下载链接】firstmateTalk to one agent. Ship with a crew.项目地址: https://gitcode.com/gh_mirrors/fi/firstmate
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考