会话管理——和 AI 的每次对话都有记录
Hermes Agent 官方教程第八篇:会话。多平台同步、跨设备接续。
会话管理——和 AI 的每次对话都有记录
你有没有过这种经历:跟 AI 聊到一半,不小心关掉了终端,或者切到了别的窗口,回来发现啥都没了?别慌,Hermes Agent 早就帮你把每一句话都记在小本本上了。
所有对话,自动存档
不管你是用命令行、Telegram、Discord、Slack,还是微信、钉钉、飞书……只要是跟 Hermes 说过的话,都会自动保存成一个会话(Session)。这些记录存在本地的 SQLite 数据库里(~/.hermes/state.db),里面不光有完整的聊天记录,还有:
- 会话标题(方便你认出来这是哪次对话)
- 用的什么模型、什么配置
- 系统提示词的快照
- 输入输出了多少 token
- 开始和结束的时间
简单说,每次对话的来龙去脉,Hermes 都记得清清楚楚。
不是所有东西都往上下文里塞
你可能会担心:那是不是每次对话都要把之前所有内容重新发给模型?不是的。Hermes 很聪明,它只把当前需要的部分发给模型。
比如你发了一张图片让 AI 做表情包,Hermes 会先看一眼图片、跑个脚本处理一下,但不会把原始图片反复塞进后续的对话。真正让上下文膨胀的,往往是那些大段粘贴的日志、超长的工具输出、反复贴的代码 diff。所以,能用摘要、文件路径、局部摘录解决的,就别整段往聊天里甩。
命令行里怎么恢复会话?
这是最常用的功能,记住这几个命令就够了:
# 继续最近一次 CLI 会话
hermes --continue
hermes -c
# 按名字恢复(比如你给会话起过标题)
hermes -c "我的项目"
# 按 ID 恢复指定会话
hermes --resume 20250305_091523_a1b2c3d4
# 恢复最近一次会话
hermes --resume latest
有个贴心小细节:hermes -c 是感知终端的。你在两个终端窗口里各开一个会话,在哪个窗口执行 -c,就恢复哪个窗口自己的对话,互不干扰。这功能靠的是每个终端留下的“面包屑”文件,如果超过 30 天没用,就会自动回退到“恢复最近会话”的逻辑。
另外,恢复一个闲置会话会采用该 profile 的当前配置:工具集、MCP 服务器、terminal 设置、max_turns、检查点和默认推理强度。会话本身则保留原来的模型、启动参数,以及你做过的 /reasoning 或工作区改动。任何差异都会开启新的提示词缓存前缀,所以恢复后的第一轮会更贵一些。改动 terminal 设置(哪怕只是 terminal.timeout 这种无关项)也会把之前的 cd 重置回会话目录。
恢复会话时,Hermes 还会在输入框上方显示一个对话回顾面板,把之前的用户消息和 AI 回复简要列出来(长消息会截断、工具调用会折叠成数量),让你快速找回上下文。想关掉它,可以在 ~/.hermes/config.yaml 里设置 display.resume_display: minimal。
给会话起个名字
想让会话更好找?用 /new 开新会话的时候,直接带上名字:
/new payments-refactor
这样以后就能用 hermes -c "payments-refactor" 秒速找回。如果同名会话有好几个(比如 my project、my project #2),Hermes 会自动恢复最新的那个。
会话太长怎么办?
聊得太多,上下文塞满了?用 /compress 压缩一下,把长会话精简成摘要。想彻底开新话题,就用 /new。要是想清理旧会话,hermes sessions prune 可以删掉已结束的会话。
如果只是数据库文件变大了,先试试无损优化:
hermes sessions optimize
这个命令会合并 FTS5 索引段、压缩数据库文件,但不会动任何会话数据。注意:网关运行时,prune 会请求网关删除它选中的会话;而 optimize,以及没有网关运行时的 prune,在别的 Hermes 进程(桌面端、面板、cron)正占用 state.db 时都会拒绝执行——先停掉那个进程,或者加上 --force。另外记住:压缩是减少上下文,不是删除隐私记录。
会话不会自己过期,但该开新会话时还是要开
网关(gateway)里的会话不会因为闲置或到了某个时间点就自动重置——想开新对话,得自己用 /new 或 /reset。上下文压缩仍然是自动的。
小提示:核心已经不再读取旧的
session_reset配置、重置策略覆盖项和重置定时器环境变量。如果你的配置里还把session_reset.mode设成idle、daily或both,网关启动和hermes doctor都会给出警告。想保留基于时间的重置,可以装官方目录插件:hermes plugins install hermes-session-reset-policy,它读的是同一个配置块,行为不变。
不过,正因为会话不会自己结束,很容易一个会话用上好几周。这样虽然能跑,但会悄悄拖累“自我学习”的效果、还会推高成本:
- 记忆只在会话边界才发挥作用。
MEMORY.md/USER.md是在会话开始时注入的,session_search也是用来找回已经掉出上下文的内容。在一个永不结束的会话里,所有东西都还在上下文里,Agent 就没理由去查记忆——那套“自我学习”机制基本跑不起来。记忆蒸馏(重置前的保存)也只有在会话真正结束时才会发生。 - 成本随历史增长。 压缩能让长会话继续可用,但每一轮仍然要带上一个很大的(压缩过的)前缀。一个带蒸馏记忆的新会话,几乎总比一个月前的老会话更便宜。
实用建议:完成一个任务或话题,就结束这个会话。在自然的停顿点运行 /new(可以带名字,比如 /new payments-refactor)——每天一次或每个项目一次都行。重置前,如果这次工作产生了值得保留的偏好或流程,可以主动让 Agent “把值得记的都记下来”;它会自动从即将过期的会话里保存记忆和技能,但明确提醒一下效果更好。重启机器或网关不算边界——同一个会话会继续。
改了 SOUL.md 或技能,怎么让“老会话”用上新提示词?
一个持续中的会话,每一轮发的都是它第一轮时生成的那份系统提示词——固定不变的字节正是让提供商能复用提示缓存的原因。副作用是:你后来改的 SOUL.md、新装或改过的技能、项目上下文文件(AGENTS.md 之类)都进不到已经在跑的会话里。重启网关也没用,网关会把每个会话存着的提示词原样恢复。以前唯一的办法是 /new,但那连对话一起丢了。
现在可以这样:
# 让当前 profile 的所有会话都换上新提示词(加 -p <profile> 换别的 profile)
hermes sessions refresh-prompts
# 只让某一个会话换(ID 或唯一前缀)
hermes sessions refresh-prompts --session SESSION_ID
这条命令只是在 profile 的 state.db 里记一个刷新请求,并打印有多少会话持有存储提示词。每个会话在下一轮才真正从当前源重建系统提示词,然后才发消息;对话历史、会话 ID 和工具列表都保持原样,重建后的提示词会被存下来,之后每轮照旧逐字节复用。
- 代价:每个被刷新的会话要付一次提示缓存未命中的钱(就重建的那一轮),之后又恢复缓存。
- 不用重启:请求在每轮开始时都会读,内存里的 agent 和从数据库恢复的 agent 都认;重启也照样生效。所以更新脚本在替换网关前后调用都行。
- 重建的是整个系统提示词:记忆和
USER.md块也会在这一轮同步到最新保存的状态;工具定义不变。请求之后新建的会话本来就带新提示词,不会再重建。 - 不打这条命令就什么都没变:会话继续回放它存着的提示词。
没人关掉的本地会话会被自动收尾
CLI、TUI 或桌面端会话,如果最后一个查看窗口消失却没正常关闭(终端被杀、连接断了、网关停止或重启时它还开着),网关会在 sessions.orphan_end_grace_s 秒(默认 300,0 表示永不结束)后把它结束掉,结束原因记为 ws_orphan_reap(重启后收尾的是 startup_orphan_reap,且至少要等它最后一次活动满 6 小时)。这些都可以关:dashboard.ws_orphan_reap_grace_s: 0 关掉前者,dashboard.startup_orphan_sweep: false 关掉重启后的清扫。还在跑的回合会跑完再结束;如果它停在被打断的回合上,则要你自己点“放弃”才关。宽限期内重新连上的查看窗口会把它保住。结束是无损的:会话仍可恢复,下一条消息会用同一个 ID 重新打开。
会话来源一览
Hermes 支持超多平台,每个会话都会标记来源:
| 来源 | 说明 |
|---|---|
cli |
命令行交互 |
oneshot |
一次性的非交互运行(hermes chat --oneshot -q、-Q、hermes -z 等)。它不会出现在 TUI、桌面端和面板的会话选择器里,但算作 CLI 历史,hermes -c / --resume latest 能接着它继续,hermes sessions list 也看得到 |
telegram |
Telegram |
discord |
Discord |
slack |
Slack |
whatsapp |
|
weixin |
个人微信 |
wecom |
企业微信 |
dingtalk |
钉钉 |
feishu |
飞书 |
email |
邮件 |
cron |
定时任务 |
api-server |
API 请求 |
kanban |
看板调度任务(只在看板上出现,会话选择器里隐藏) |
tool |
第三方集成(--source tool),会话选择器里隐藏 |
homeassistant |
Home Assistant 事件(插件) |
总共二十多种,基本覆盖了你能想到的所有入口。顺带一提,会话中途被压缩后,来源标签会保持不变——比如 tool 或 oneshot 会话压缩出来的子会话,还是同样的来源、同样的可见性。
把 CLI 会话交接给聊天平台
在 CLI 会话里用 /handoff <platform>,可以把当前对话直接转到某个聊天平台的 home 频道,Agent 会从 CLI 停下的地方接着聊——同一个会话 ID、完整的对话记录、工具调用全都在。
# 在 CLI 会话里
/handoff telegram
前提是目标平台已启用、并且设置过 home 频道(在目标聊天里跑一次 /sethome)。交接完成后,CLI 会提示你用 /resume <标题> 随时把会话拉回桌面端继续。
不同平台落地的方式略有不同:Telegram 会开一个新的论坛话题(如果机器人主人通过 BotFather 启用了 Threaded Mode,就是私聊话题;否则是论坛超级群的话题),Discord 建一个自动归档的线程,Slack 发一条种子消息当线程锚点,Matrix 用事件 ID 作为线程根;而 WhatsApp、Signal、SMS 没有原生线程,就直接落在 home 频道里。另外提醒一句:对于不支持线程、又是多人共享群组 home 频道的平台,交接后的会话会按私聊方式处理——自聊 home 频道(最常见的情况)没问题,但真正共享的群聊就不太理想了。
导出与删除会话
hermes sessions export 可以把会话导出成文件。Markdown/QMD 格式会为每个导出的会话写一个 .md 或 .qmd 文件,外加一个 manifest.jsonl,里面记录文件路径、消息数、血缘 ID 和 SHA-256。批量导出至少要带一个过滤条件,光秃秃的批量导出会被拒绝。--delete-after-verified 只对 --session-id 生效,并且必须加 --yes。因为删除父会话会连带删掉它的委派/子代理会话,这个模式会先把每个委派会话导出到单独文件并校验,然后才动手删除。Markdown/QMD 文件保存的是会话展示的完整历史,包括被就地压缩归档的轮次。删除时会在同一个数据库事务里再次比对这份展示记录和委派集合;期间只要发生追加、重写、回退、压缩或委派变化,删除就会被拒绝。同样的展示历史规则也适用于 --format html、--only user-prompts(Markdown 或 JSONL 输出)以及 /save md|html。
这里有个容易搞混的地方,值得单独说清楚:
- 完整备份:
hermes sessions export(不带--only)和/save json(CLI、消息平台、TUI、桌面端)导出的每一行存储记录,包括归档行。也就是说,备份里装着这个会话曾经有过的全部内容——被就地压缩归档的轮次、被回退或编辑删掉的消息,全都在。导出的文件是每行一个 JSON 对象,带完整的会话元数据和每条消息的active/compacted标记。把这些文件导回去(面板的会话导入),那些行会作为归档历史恢复,而不会重新变成活跃的模型上下文。 - 分享用的导出:想分享一段对话,用展示格式(
--format md或html),它只包含会话当前展示的历史,再配上--redact更稳妥。 - 过滤导出:会包含匹配的置顶和归档会话。置顶是防止会话被清理,而不是把它排除在备份之外。如果显式指定的
--session-id解析不到,导出会以非零状态退出,并且不生成任何输出文件。 --redact:会在写入前清洗消息内容和工具输出里的密钥(API key、token、凭证)——准备分享的导出都建议加上。
另外,每个会话的备份都是在内存里拼出来的,所以存储行数超过 sessions.max_export_messages(默认 20000 条,设成 0 关闭这道闸)的会话会被拒绝导出。这个上限是按会话算的,而且统计的是每一行存储记录(含归档),所以一个被重度压缩、活跃尾巴很短的会话也可能超限。TUI/桌面端的 /save 被这道闸拦下时会返回错误码 4131;hermes sessions export 对这类会话仍然可用,但它的 JSON/JSONL 备份同样要求每个会话不超过这个上限。面板的 Sessions 页面里的导出动作是流式写出的,不受这个上限限制。
还有一点:删除一个还在运行中的聊天里打开的会话,并不会停掉那个聊天:它下一次保存会用同一个 ID 把会话重新建出来,带上完整的内存记录。想让会话彻底消失,先把聊天关掉。
被压缩拆成多个会话的对话会作为一个整体来清理:只要还有较新的分段存在,较早的分段就会保留。归档也是同理——一段被压缩的对话会通过它的活跃末端整体归档,旧的压缩分段不会单独按自己的年龄匹配,所以一个还在活跃的聊天不会因为历史太长而被隐藏。
归档、隐藏与恢复的小规矩
会话的“置顶 / 归档 / 隐藏”这几个标记,在恢复时会被保留:
- 恢复一个导出(面板导入,或者某个 profile 收养一个“流浪”会话)时,置顶、归档、隐藏标记都会保留,连“这个归档是不是
sessions.auto_archive清扫出来的”也会一起记住。 - 恢复回来的置顶会话,依然豁免于保留期清理;收养回来的 Bot Mode 聊天,依然保持隐藏;恢复回来的清扫归档,你恢复它时它还是会回来。
- 比这些标记更老的导出文件,恢复后就变成普通的、未置顶、可见的会话。
另外,被 sessions.auto_archive 闲置清扫隐藏的聊天,一旦重新活跃就会自己回来——你恢复它,或者新活动把它压进一个新的延续会话时都算。而你自己手动归档的聊天(侧边栏、API 或 hermes sessions archive),就得你自己取消归档才会回来。
修复受损的存储提示词
旧版本受 #122822 影响,可能让网关的清理流程或网关 /compress 把一个分离的维护代理的“精简工具集”系统提示词写到了活跃会话上。装上 PR #122825 的根因修复后,可以用 hermes sessions repair-prompts 找出已经被写坏的行。
这个扫描很保守:只有当存储的提示词缺少 ## Skill Safety 指引并且持久化的 tools[] 固定项里包含 skill_manage(它总会带上那段指引)时,才会提议修复。没有可读固定项、或者只有 memory 固定项(这也是合法的 toolsets: [memory] 配置)的行会被报告为无法验证,扫描不会改动它们;需要的话可以按 SESSION_ID 显式清理。只有 memory 的行也会自己变得可修复:一旦会话被恢复,它的 tools[] 固定项会重新固定完整的工具面,之后扫描就能识别了。
小结
会话管理就是 Hermes 的“记忆系统”——自动存档、按名恢复、跨平台同步。下次再也不用担心聊到一半丢了上下文,hermes -c 一下,接着聊!
官方界面截图
📖 官方文档
本文根据 Hermes Agent 官方文档编写,原文见:官方文档 › user-guide/sessions