База состояния и восстановление FTS
Пошаговое руководство для пользователя (что остановить, что за файлы лежат рядом со state.db,
почему служебные команды отказываются работать, пока жив процесс-писатель) — на странице
Восстановление хранилища сессий.
state.db хранит два разных класса данных:
sessionsиmessages— канонический транскрипт.- Таблицы
messages_fts*и их триггеры синхронизации — производные поисковые индексы.
Производные индексы можно временно отсоединить. Они не должны превращать живую запись сообщения или поиск в неограниченную перестройку всего транскрипта.
Поведение вживую, когда повреждён FTS
Если запись в FTS или поиск сообщает об ошибке класса «повреждение»,
SessionDB:
- записывает долговременный маркер
fts_stale; - в той же транзакции удаляет триггеры синхронизации FTS;
- повторяет канонические записи уже без приёмников производного индекса; и
- отдаёт результаты поиска из канонических строк через запасной путь
LIKE.
Упавшая живая операция никогда не запускает FTS5('rebuild'). Существующее
владение восстановлением не меняется: более позднее открытие SessionDB может
выполнить перестройку под межпроцессной блокировкой допуска и защитой от
чужого держателя. Если такая защищённая перестройка выполниться не может, FTS
остаётся отсоединённым, канонические записи продолжают работать, а
hermes doctor сообщает явную команду починки.
Поведение вживую, когда повреждён сам файл
Если живая запись сообщает голый SQLITE_CORRUPT / SQLITE_NOTADB
(database disk image is malformed, file is not a database) без следов
FTS, повреждение сидит в каноническом B-дереве, схеме или списке свободных
страниц. Тогда SessionDB помещает этот дескриптор в карантин
(StateDbCorruptError):
- упавшая запись пробрасывает типизированную ошибку, и ничего не повторяется;
- последующие записи через этот дескриптор падают сразу, не трогая файл;
- дескриптор никогда не переоткрывает своё соединение после
close(); и close()пропускает свой явный чекпоинт WAL.
Защита — в том, чтобы остановить записи. В реальной эксплуатации дескриптор,
который продолжал писать ~50 минут после первой структурной ошибки, при
завершении работы записал чекпоинтом 15 страниц под неверными номерами
(страница 1 получила лист messages_fts_trigram_data) и превратил
повреждённый, но читаемый файл в файл, который больше не открывался вовсе.
Пропуск явного чекпоинта — вторая линия обороны; на Python 3.12+ карантин
также отключает собственный чекпоинт SQLite при закрытии последнего
соединения (SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE), так что файл-спутник -wal
переживает close() и остаётся для разбора. На Python 3.11 этого
переключателя нет, и SQLite всё же может выполнить один чекпоинт при
закрытии, поэтому скопируйте state.db, state.db-wal и state.db-shm
вместе, прежде чем что-либо перезапускать.
Шлюз и путь сброса агента обращаются с карантином как с подменённым файлом:
ожидающие транскрипты уходят в 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/.
Явная починка
Остановите каждый процесс, который может открыть базу профиля, прежде чем её чинить. Держите их остановленными на всё время починки и проверки.
hermes gateway stop
HERMES_HOME="$HOME/.hermes" hermes sessions repair --check-only
HERMES_HOME="$HOME/.hermes" hermes sessions repair
sessions repair по умолчанию создаёт резервную копию SQLite и выполняет
структурную работу через защищённый путь репозитория «снимок и продвижение».
Не копируйте state.db, state.db-wal и state.db-shm по отдельности через
cp: эти файлы — один живой образ 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 должны присутствовать, а число канонических строк не должно уменьшиться. Если починка не удалась, сохраните и живую базу, и указанную резервную копию; никогда не удаляйте канонические строки ради того, чтобы исчезла ошибка производного индекса.