/save 导出的会话少了一截?把压缩归档和导出上限一次讲清

你有一段跑了很久的会话,压缩(compaction)已经归档过好几轮,现在你想把它导出来存个档。在 CLI 里 /save json,一切正常;切到桌面端做同样的事,导入之后发现中间少了几段——那些被压缩归档的轮次不见了。你开始怀疑是不是自己记错了,或者哪次 /undo 把它删了。
这是两个问题叠在一起:同一个 /save,三个界面(CLI、消息平台、桌面/TUI)过去走的不是同一条路径;而「该导出什么」和「导出多大算太大」这两条规则,也因此各说各话。修复把这些收敛到了一处。
三种 /save,以前三种结果
/save 支持三种格式:json(完整快照)、md(可读转录)、html(单文件分享页)。桌面端和 TUI 的 /save 走的是 RPC session.save,而 CLI 与消息平台的 /save 走的是另一套代码。差别在导出投影(projection)上:
- 转录(
md/html)要的是「用户看到的东西」,所以必须包含被就地压缩归档的轮次; - JSON 快照是「导入时要还原的东西」,所以它要带上每一行存储记录和它的
active/compacted标记。只导「当前活跃」的行,会把每一段被压缩归档的对话都丢掉;而带着标记导回去,导入时它们会恢复成归档状态,不会被当成活跃上下文重放。
以前桌面/TUI 那条 RPC 路径没有严格按这条规则做,于是同一个会话在两个界面导出的内容不一样。
现在的规则:一条路径,三个界面共用
修复把 CLI 那条已经正确的逻辑抽成了一个共享函数 load_save_snapshot(db, session_id, fmt),放在 hermes_cli/session_export.py 里,三个界面都调它。它做三件事:
- 按格式选投影:转录
include_compacted=True,JSONinclude_inactive=True; - 对 JSON 快照执行删除已撤销行:只有
active或compacted的行留下,被/undo、rewind、edit 移除的行不收——这是刻意的,导入时不该把用户撤销过的内容带回来; - 执行导出上限检查(下面细说)。
顺带修掉的还有两个细节。第一,JSON 快照现在是规范导出形状,和 CLI 一致,而不是桌面端自己的旧形状;只有在会话没有存储记录、或读取失败时,才退回内存中的历史,并且这时也写同样的形状(id、model、started_at、system_prompt、messages)。第二,它读的是当前活跃 agent 正在写的那一行,这样压缩发生旋转(rotation)之后,不会导出已经过时的父行。
导出上限:sessions.max_export_messages
把一条 10 万行的会话一次性materialize 到内存里,是能打爆进程的。所以有一个上限:
sessions:
max_export_messages: 20000 # 默认值;0 表示关闭该保护
这个限制按会话检查,所以「全库备份一堆小会话」不会被误伤,只有单条超大 session 会被拦。本次修复之后,桌面/TUI 的 /save json 也遵守它,并且:
- 被拦下时,RPC 返回的错误码是 4131(不是笼统的 5011),错误信息与 CLI 一致;
- 在 turn-isolated(计算宿主)会话里,宿主会把处理器的错误码放进
control.error一并回传,父进程再透传,所以远程执行的会话也能拿到准确的原因; - 错误提示里会直接告诉你可以把
sessions.max_export_messages: 0写进config.yaml来关掉这个保护。
最后一条容易被忽略但很重要:上限取的是这条会话所属 profile 自己的配置。桌面端现在会按那个 profile 的 config 去读它自己的 state.db,所以「次要 profile 的上限」是它自己的,不是你主 profile 的。
对着场景该怎么选
知道了规则,选格式就简单了:
| 你的目的 | 用什么 | 为什么 |
|---|---|---|
| 存档 / 迁移到另一台机器 | /save json |
完整快照,带归档标记,导入可还原 |
| 发给同事看 | /save html 或 /save md |
可读转录,包含压缩归档的轮次 |
| 会话特别大、导出被拒 | 先 hermes sessions export 单条导出,或把 max_export_messages 调大/设 0 |
上限是按会话判定的 |
| 要贴到公开渠道 | 任一格式加 redact |
写文件前会抹掉 API Key、token 等凭据 |
这里最值得记住的一点是:「导出」有两个不同的目的,规则也刻意不同。 转录是给人看的,越接近你当时看到的越好,所以归档的轮次照收;JSON 是给机器还原的,所以它要保留标记、剔除撤销——如果你曾经以为「JSON 里少了内容就是丢了」,这次的规则说明能帮你把这个误会划掉。
什么时候能用上
这次修复于 2026 年 10 月 3 日合入主分支。当前最新正式版是 v0.21.5(tag v2026.9.24),所以它目前只在 main 上;sessions.max_export_messages 这个配置项本身在正式版里已经存在,现在就能用。跟踪 main 的安装下次 hermes update 后会一致。
想更系统地理解会话这件事,可以看会话导出与保存那篇(/save 与 hermes sessions export 的完整对照)和会话边界指南;如果你在意的是压缩机制本身,压缩精度那篇和一次性压缩调用讲得更透。命令层的细节在命令参考。
小结
这次修复的价值不在某个新功能,而在消除歧义:以前你无法确定「桌面端导出的 JSON 少了内容」是 bug 还是设定,现在三条路径一套规则、两种投影各有明确理由,超限时会给你一个明确的错误码和一句能直接照做的建议。如果你有定期导出会话做备份的习惯,值得顺手检查一件事:你的备份流程里写的是哪个界面、哪个格式——以及它导出的东西,真的能原样导入回去吗。