状态数据库与 FTS 恢复
面向用户的操作指南(该停掉什么、state.db 旁边的文件是什么、为什么维护命令在有写入者存活时拒绝运行)见会话存储恢复。
state.db 存储两类不同数据:
sessions和messages是规范记录。messages_fts*表及其同步触发器是派生搜索索引。
派生索引可以暂时分离。它们绝不能把一次存活的消息写入或搜索变成一次无界的全记录重建。
FTS 损坏时的存活行为 {#live-behavior-when-fts-is-corrupt}
若某次 FTS 写入或搜索报告损坏错误类别,SessionDB 会:
- 记录持久的
fts_stale标记; - 在同一事务中移除 FTS 同步触发器;
- 在没有派生索引汇的情况下重试规范写入;
- 通过
LIKE回退从规范行提供搜索。
失败的存活操作绝不运行 FTS5('rebuild')。既有的恢复归属保持不变:后续一次 SessionDB 打开可能在跨进程准入锁和外部持有者守卫下重建。若该受守卫重建无法运行,FTS 保持分离,规范写入仍可用,hermes doctor 报告显式修复命令。
文件本身损坏时的存活行为 {#live-behavior-when-the-file-itself-is-corrupt}
若一次存活写入报告裸的 SQLITE_CORRUPT / SQLITE_NOTADB(database disk image is malformed、file is not a database),且无 FTS 来源,则损坏在规范 B 树、schema 或 freelist 中。SessionDB 随即隔离该句柄(StateDbCorruptError):
- 失败的写入传播类型化错误,什么都不重试;
- 该句柄上的后续写入立即失败,不触碰文件;
- 句柄在
close()后绝不重开其连接; close()跳过其显式 WAL checkpoint。
停止写入就是保护措施。在现场,一个句柄在首次结构性错误后继续写了约 50 分钟,在关闭时按错误的页码 checkpoint 了 15 页(第 1 页收到一个 messages_fts_trigram_data 叶子),把一个"损坏但可读"的文件变成了一个根本打不开的文件。跳过显式 checkpoint 是第二道防线;在 Python 3.12+ 上,隔离还会禁用 SQLite 自己的最后连接 checkpoint(SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE),使 -wal sidecar 在 close() 后保留以供取证。在 Python 3.11 上该开关不可用,SQLite 仍可能在关闭时 checkpoint 一次,因此在重启任何东西之前,把 state.db、state.db-wal 和 state.db-shm 一起复制。
网关和智能体 flush 路径把隔离当作文件被替换处理:待处理记录进入 sessions/<id>.jsonl 和网关 pending_messages/ 假脱机,而非重试队列;FTS 一次性重建绝不在损坏文件上运行。隔离是按进程的——共享句柄对每个持有者都保持中毒,直到进程在已修复或已恢复的文件上重启。不要在网关仍在运行时运行 hermes doctor --fix。后续步骤:
hermes gateway stop
HERMES_HOME="$HOME/.hermes" hermes sessions recover --source "$HOME/.hermes/state.db" --inspect-only
# 若可恢复:
HERMES_HOME="$HOME/.hermes" hermes sessions recover --source "$HOME/.hermes/state.db" --output "$HOME/recovered-state.db"
或从 state-snapshots/ 恢复最新快照。
显式修复 {#explicit-repair}
修复前,停掉每个能打开该 profile 数据库的进程。在完整修复和验证窗口内保持它们停止。
hermes gateway stop
HERMES_HOME="$HOME/.hermes" hermes sessions repair --check-only
HERMES_HOME="$HOME/.hermes" hermes sessions repair
sessions repair 默认创建一个 SQLite 备份,并通过仓库受守卫的"快照-提升"路径做结构性工作。不要用 cp 独立复制 state.db、state.db-wal 和 state.db-shm;这些文件是一个存活的 SQLite 映像。
修复后,在重启网关之前验证健康探针、陈旧标记、触发器集和规范行数:
HERMES_HOME="$HOME/.hermes" hermes sessions repair --check-only
sqlite3 "$HOME/.hermes/state.db" \
"SELECT key, value FROM state_meta WHERE key = 'fts_stale';"
sqlite3 "$HOME/.hermes/state.db" \
"SELECT type, name FROM sqlite_master WHERE name IN
('messages_fts_insert','messages_fts_update','messages_fts_delete')
ORDER BY name;"
sqlite3 "$HOME/.hermes/state.db" \
"SELECT 'sessions', COUNT(*) FROM sessions
UNION ALL SELECT 'messages', COUNT(*) FROM messages;"
标记查询应不返回行,应存在预期的 FTS 触发器,规范行数不得减少。若修复失败,保留存活数据库和报告的备份;绝不删除规范行来让派生索引错误消失。