fix(sessions): advertise v1 FTS storage rebuild

Offer optimize-storage for tool-call-inclusive trigram layouts in doctor and update notices.
This commit is contained in:
Andrew Wikel
2026-08-21 00:17:58 -05:00
committed by kshitij
parent 46bcad0c24
commit c917374ae2
4 changed files with 72 additions and 13 deletions
+6 -6
View File
@@ -35,6 +35,7 @@ from hermes_cli.colors import Colors, color
from hermes_cli.models import _HERMES_USER_AGENT
from hermes_cli.vercel_auth import describe_vercel_auth
from hermes_constants import OPENROUTER_MODELS_URL
from hermes_state_common import FTS_STORAGE_VERSION
from utils import base_url_host_matches
@@ -496,21 +497,20 @@ def _render_state_db_stats(stats: dict, holders=None) -> list:
"optimize-storage' with the gateway stopped)",
))
# Advisory: oversized database. Suggest auto_prune, and — when the v23
# FTS rebuild is pending OR the DB still carries the legacy inline
# trigram layout (fts_storage_version marker absent) — the offline
# Advisory: oversized database. Suggest auto_prune, and — when the FTS
# rebuild is pending OR the DB predates the current trigram layout — the offline
# optimize-storage pass that migrates/compacts the FTS indexes.
if logical is not None and logical > STATE_DB_SIZE_WARN_BYTES:
detail = (
"consider enabling sessions.auto_prune in config.yaml "
"to bound growth"
)
legacy_trigram = (
stale_trigram = (
fts is not None
and fts.get("messages_fts_trigram")
and stats.get("fts_storage_version") is None
and (stats.get("fts_storage_version") or 0) < FTS_STORAGE_VERSION
)
if stats.get("fts_rebuild_pending") or legacy_trigram:
if stats.get("fts_rebuild_pending") or stale_trigram:
detail += (
"; run 'hermes sessions optimize-storage' offline "
"(with the gateway stopped) to compact FTS storage"
+10 -7
View File
@@ -1050,9 +1050,9 @@ def _print_curator_first_run_notice() -> None:
def _print_fts_optimize_available_notice() -> None:
"""Advertise the opt-in v23 search-index optimization after `hermes update`.
Only fires when the current profile's state.db is still on the legacy
(pre-v23) inline FTS layout. Leads with the reclaimable-space figure and
points at the exact command. Honors ``sessions.fts_optimize_notice``:
Only fires when the current profile's state.db still needs an FTS storage
rebuild. Leads with the reclaimable-space figure and points at the exact
command. Honors ``sessions.fts_optimize_notice``:
``advise`` (default) prints an advisory notice, ``require`` prints a
firmer required-upgrade notice, ``off`` suppresses it. Silent for
fresh/already-optimized installs.
@@ -1088,13 +1088,17 @@ def _print_fts_optimize_available_notice() -> None:
return
db = None
interrupted = False
needs_upgrade = False
try:
db = SessionDB(db_path=db_path, read_only=True)
# read_only opens skip schema init, so probe the layout directly.
# read_only opens skip schema init, so probe the stored layout directly.
row = db._conn.execute(
"SELECT sql FROM sqlite_master "
"WHERE type = 'table' AND name = 'messages_fts'"
).fetchone()
needs_upgrade = bool(row) and getattr(
db, "_db_needs_fts_storage_upgrade"
)(db._conn)
# An interrupted `optimize-storage` run: the table is already the
# v23 shape, but backfill markers / demoted trash tables remain.
# Offer the command again — re-running resumes and finishes it.
@@ -1120,9 +1124,8 @@ def _print_fts_optimize_available_notice() -> None:
db.close()
except Exception:
pass
sql = (row[0] if row else "") or ""
if not sql or ("tool_name" in sql and not interrupted):
# v23 layout already present (fresh/optimized) — nothing to offer.
if not needs_upgrade and not interrupted:
# Current layout already present (fresh/optimized) — nothing to offer.
return
if interrupted:
@@ -0,0 +1,42 @@
"""Regression coverage for FTS storage upgrade discoverability."""
import sqlite3
from types import SimpleNamespace
def test_update_notice_offers_v1_trigram_tool_calls_rebuild(tmp_path, monkeypatch, capsys):
"""A deployed v1 trigram projection still receives the opt-in notice."""
from hermes_cli import update_cmd
import hermes_constants
import hermes_state
db_path = tmp_path / "state.db"
db_path.touch()
conn = sqlite3.connect(db_path)
conn.executescript(
"""
CREATE TABLE state_meta (key TEXT PRIMARY KEY, value TEXT);
CREATE TABLE messages_fts (content TEXT, tool_name TEXT, tool_calls TEXT);
CREATE TABLE messages_fts_trigram (content TEXT, tool_name TEXT, tool_calls TEXT);
"""
)
class FakeSessionDB:
def __init__(self, **_kwargs):
self._conn = conn
def close(self):
pass
_db_needs_fts_storage_upgrade = staticmethod(
hermes_state.SessionDB._db_needs_fts_storage_upgrade
)
monkeypatch.setattr(hermes_constants, "get_hermes_home", lambda: tmp_path)
monkeypatch.setattr(hermes_state, "SessionDB", FakeSessionDB)
monkeypatch.setattr(update_cmd.Path, "stat", lambda _path: SimpleNamespace(st_size=512 * 1024 ** 2))
update_cmd._print_fts_optimize_available_notice()
assert "hermes sessions optimize-storage" in capsys.readouterr().out
conn.close()
+14
View File
@@ -236,6 +236,20 @@ def test_render_large_db_legacy_trigram_suggests_optimize():
assert "optimize-storage" in blob
def test_render_large_db_v1_trigram_suggests_optimize():
from hermes_cli.doctor import STATE_DB_SIZE_WARN_BYTES, _render_state_db_stats
lines = _render_state_db_stats(
_base_stats(
logical_size_bytes=STATE_DB_SIZE_WARN_BYTES + 1,
fts_storage_version=1,
),
holders=None,
)
blob = " ".join(" ".join(str(p) for p in line) for line in lines)
assert "optimize-storage" in blob
def test_render_does_not_duplicate_legacy_wal_warning():
"""A large WAL must NOT warn here: doctor's pre-existing WAL check
(50 MB threshold, with a --fix checkpoint) already covers it, and a