连续运行3天不丢字:ainovel-cli 多Agent小说生成的 Step 级 Checkpoint 与 Saga 原子提交全解析
【免费下载链接】ainovel-cli✨多agent实现全自动AI小说生成项目地址: https://gitcode.com/gh_mirrors/ai/ainovel-cli
ainovel-cli是一个用多 Agent 协作实现全自动AI 小说生成的开源工具:架构师规划、写手产出、编辑审校,一条命令可以连续跑数天、产出数百章正文。长时运行最怕的就是"跑到一半崩了,前面全白写"。它的解法只有两句话——Step 级 checkpoint记录每一步落盘事实,Saga 原子提交保证章节提交中断后可从断点重放。这篇文章带你彻底看懂这套"不丢字"机制,不需要读一行源码也能收获。
上面是它真实的运行界面:左侧是运行状态与 token 预算,中间是实时事件流(每一章都经历 plan → draft → check → commit 的完整链路),右侧是因果链回溯。跑 3 天、写 170 多章,随时可以中断、重启、续跑。
为什么"多 Agent 长跑"最怕丢字
传统脚本挂了重跑一遍就行,但 AI 小说生成不一样:
- 每一步都烧钱:一章正文要调用多次大模型,重试成本是真金白银
- 上下文不可再生:模型不会"记得"上一轮写了什么,断点信息必须来自磁盘
- 多 Agent 交错执行:写手刚提交、架构师还没规划下一章,崩溃可能发生在任何缝隙里
所以 ainovel-cli 的设计铁律是:完成的唯一证据是 checkpoint 写入,而不是"模型说自己完成了"。
一、Step 级 Checkpoint:只追加的"进度账本"
1. 每个关键步骤都打一个 checkpoint
从定义 internal/domain/checkpoint.go 可以看到,一条 checkpoint 只记录五个事实:
| 字段 | 含义 | 例子 |
|---|---|---|
Seq | 全局单调递增序号 | 1024 |
Scope | 作用域:章节/弧/卷/全局 | chapter:172 |
Step | 完成了哪一步 | plan/draft/commit |
Artifact | 产物文件 | chapters/172.md |
Digest | 产物内容指纹 | 用于判重 |
所有 checkpoint 以只追加(append-only)方式写入meta/checkpoints.jsonl(见 internal/store/checkpoints.go)。只追加意味着:永远不需要修改历史,日志坏了也不会互相污染,重启时从上往下读一遍就能完整重建进度。
每章写作会依次留下多个 step:plan(章节计划)→draft(草稿)→commit(终稿提交),弧末还有review、arc_summary。崩溃恢复时,Engine 读到的不是"我上次干到哪一步"的模糊记忆,而是磁盘上的确定事实(恢复入口见 internal/host/resume.go)。
2. 幂等设计:重复执行零副作用
这是"不丢字"的第一道防线。每个写类工具执行前都会先查 checkpoint:如果当前 scope 最新的Step + Digest与本次完全相同,直接返回已有产物,不再重做。
效果是什么?
- 网络抖动导致的重试 → 安全
- 崩溃后恢复的重复派发 → 安全
- 同一个 step 被派发三次 → 磁盘上只有一条记录(相同
Scope+Step+Digest视为幂等,不产生新行)
换句话说,你可以放心地反复重启进程,引擎不会把同一章写两次、不会把大纲推进两次。
3. CheckpointDeltaGuard:防止 Agent"嘴上说完成"
大模型偶尔会"幻觉式收工"——活没干完却结束了本轮。为此每个 Worker(架构师/写手/编辑)都挂了一个事实护栏 internal/agents/guard/subagent_guards.go:本轮开始时的 checkpoint 是基线,结束前必须看到对应 step 的新 checkpoint,否则拒绝收工;连续拦 3 次直接升级终止。
弱模型死循环到这里也会被兜住——这是它敢让流程"无人值守"跑 3 天的底气之一。
二、Saga 原子提交:一章正文的"跨文件事务"
1. 为什么需要 Saga
一章提交要同时写多个文件:终稿chapters/chXX.md、章节摘要、时间线、伏笔账本、角色关系、进度progress.json……文件系统没有跨文件事务,写到一半断电怎么办?
答案是把这次提交做成一条Saga(持久化意图):先把"完整提交意图 + 正文快照"冻结到meta/pending_commit.json,之后每一步都基于这份冻结载荷推进,任何时刻崩溃都能精确续上。核心实现就在 internal/tools/commit_chapter.go。
2. 四个阶段,每走一步都留痕
PendingCommit的状态机定义在 internal/domain/commit.go,推进顺序是:
① started 冻结完整载荷 + 正文快照落盘 ② state_applied 终稿/摘要/时间线/伏笔等状态写入完成 ③ progress_marked 进度标记完成 + 提交结果固化 ④ signal_saved commit checkpoint 追加完成 最后:清除 pending_commit,提交闭环两个关键细节值得新手理解:
- 正文快照只认第一次。恢复时一律重放首次落盘的冻结载荷,禁止采用重启后模型"重新生成"的参数或已被覆盖的草稿——否则会出现"旧状态 + 新正文"的错配,这正是丢字的典型来源。
- checkpoint 必须先于清除意图。只有
commitcheckpoint 落盘后才会删除pending_commit.json(见 commit_chapter.go 注释)。反过来,重启后只要还能看到 pending_commit,就知道"上一轮提交没走完",继续按阶段补齐即可。
3. 崩溃在任何一个缝隙,都只"续"不"重"
假设崩溃发生在 ② 和 ③ 之间:终稿文件已经写了,但进度还没标记。重启后引擎读到pending_commit处于state_applied,于是跳过已完成的写入,直接从"更新进度"继续。因为每一步都是幂等的(写入前查 checkpoint / 查 completed 列表),重放不会产生重复章节。
这就是标题里"3 天不丢字"的工程含义:不是"永不崩溃",而是任何崩溃窗口内的每一步都已持久化意图,恢复 = 按断点重放。
三、单文件原子写:最底层的最后一道保险
上面所有"落盘"都有一个共同前提:单个文件要么写成功、要么像没写过。ainovel-cli 用的是经典的temp 文件 + fsync + rename 原子替换方案(设计见 docs/architecture.md):
- 内容先写到同目录临时文件
fsync强制刷到磁盘rename原子覆盖目标路径
操作系统保证 rename 是原子的——断电时你只会看到"旧文件完整"或"新文件完整",永远不会有一个写到一半的残文件。
四、三层防线总结
| 层级 | 机制 | 防的是什么 |
|---|---|---|
| 文件层 | tmp + fsync + rename 原子替换 | 写一半断电产生残文件 |
| Step 层 | checkpoints.jsonl 只追加 + Digest 幂等 | 重复执行、崩溃后重放 |
| 提交层 | PendingCommit Saga 四阶段推进 | 跨文件提交中断后的状态错配 |
再配上 internal/agents/guard/subagent_guards.go 的收工护栏,"模型说完成了"永远要换算成"磁盘上有什么"。
五、延伸阅读
想深入这套体系的更多设计决策(Engine 循环、Arbiter 裁定、恢复流程),推荐按顺序读这几份仓库内文档:
- 总体架构与铁律:docs/architecture.md
- 章节推进 Gate 与许可对账:docs/chapter-advance-gate.md
- 引擎与 Arbiter 协作:docs/engine-arbiter.md
- 关键源码:checkpoint 存储 internal/store/checkpoints.go、Saga 定义 internal/domain/commit.go、提交工具 internal/tools/commit_chapter.go
小结:ainovel-cli 的"不丢字"不靠运气,靠的是把"完成"从模型的话转换成磁盘上的事实——checkpoint 记每一步、Saga 管每一次提交、原子写保每一个文件。理解了这个模式,对你自己写任何长时运行的 AI Agent 系统都有直接参考价值。
【免费下载链接】ainovel-cli✨多agent实现全自动AI小说生成项目地址: https://gitcode.com/gh_mirrors/ai/ainovel-cli
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考