Skip to content

feat(state): add sessions.trigram_fts config flag to disable the trigram FTS index - #68394

Open
fadhlillah2 wants to merge 1 commit into
NousResearch:mainfrom
fadhlillah2:feat/sessions-trigram-fts-config
Open

feat(state): add sessions.trigram_fts config flag to disable the trigram FTS index#68394
fadhlillah2 wants to merge 1 commit into
NousResearch:mainfrom
fadhlillah2:feat/sessions-trigram-fts-config

Conversation

@fadhlillah2

Copy link
Copy Markdown

What does this PR do?

Adds sessions.trigram_fts (default true) — a config.yaml flag to disable the messages_fts_trigram FTS5 index, the secondary trigram index that powers CJK/substring recall in session_search.

That index carries an INSERT/UPDATE/DELETE trigger on every message and is frequently the single largest contributor to state.db growth (on one production install: ~273 MB of a 549 MB DB — 88% of it FTS, mostly the doubled trigram data/content shadow tables). Operators who don't need CJK or substring search can now opt out; CJK/substring queries fall back to LIKE — the exact graceful path already taken when the SQLite build lacks the trigram tokenizer.

Why this approach (and why the prior attempts were closed)

This is deliberately a config knob, not an env var. Every earlier attempt at this feature used a HERMES_* environment variable and was closed under the AGENTS.md policy that reserves .env/HERMES_* for credentials and requires behavioral flags in config.yaml:

This PR is the first to implement the maintainers' stated requirement (a sessions.* config key, matching auto_prune/vacuum_after_prune/write_json_snapshots). It also folds in the two technical notes left on the closed cluster (#57761#27770): the trigger-repair count now tolerates the deliberately-absent trigram triggers, and all migration paths are covered — not just steady-state init.

Related Issue

Fixes #55233 (and addresses the size evidence in #22478 / #43690).

Type of Change

  • New feature (non-breaking change which adds functionality)

Changes Made

  • hermes_cli/config.py — new sessions.trigram_fts: True default (deep-merged at read time, no _config_version bump needed since it's an optional default).
  • hermes_state.py
    • _trigram_fts_enabled() — reads the knob via a deferred load_config_readonly() import (keeps the storage layer cycle-free; a single global read keeps every SessionDB open in agreement so the schema can't oscillate across processes).
    • _drop_trigram_fts_triggers() — trigger-only drop (leaves base FTS intact).
    • Gated all three trigram creation paths: v10 migration, v11 migration, steady-state _init_schema. When disabled, creation/backfill is skipped and existing trigram triggers are dropped so writes stop paying the cost immediately.
    • Trigger-repair heuristic now expects only the triggers the active config requires → no full-FTS rebuild loop on every open when trigram is off.
    • optimize_fts() — the deliberate, lock-held teardown point: drops the (possibly large) trigram shadow table when disabled so a following VACUUM reclaims the pages. The heavy DROP is intentionally not in __init__ (avoids contending with a concurrent writer during a plain open).
  • tests/test_hermes_state.py — two tests: disabled path (no table, base search works, no rebuild loop, idempotent reopen) and re-enable flip-back (index rebuilt).
  • website/docs/user-guide/sessions.md — documents the knob + LIKE fallback + reclaim path.

How to Test

pytest tests/test_hermes_state.py -q -k trigram_fts
  1. Default (no config) → trigram index built as before (parity).
  2. sessions.trigram_fts: falsemessages_fts_trigram never created; db._trigram_available is False; base word search still returns results; substring/CJK degrade to LIKE.
  3. Reopen while disabled → idempotent, no rebuild loop, trigram triggers stay absent.
  4. Flip back to true → trigram index rebuilt on next open.

Verified end-to-end against real runtime deps (disabled path, idempotent reopen, flip-back) plus the config-read path (trigram_fts: falseFalse, absent → True).

Checklist

Code

Documentation & Housekeeping

  • Updated docs (website/docs/user-guide/sessions.md) + inline config schema comment
  • cli-config.yaml.example — N/A (it does not enumerate the sessions.* keys; peers auto_prune/vacuum_after_prune are not listed there either)
  • CONTRIBUTING/AGENTS — N/A (no architecture change)
  • Cross-platform — no OS-specific calls added

…ram FTS index

The messages_fts_trigram FTS5 index (CJK/substring recall) carries a
per-message INSERT/UPDATE/DELETE trigger and is frequently the largest
contributor to state.db growth. sessions.trigram_fts (default true) lets
operators who don't need CJK/substring search opt out: creation is skipped
across all three schema paths (v10 migration, v11 migration, steady-state
init), existing triggers are dropped on the next open so writes stop paying
the cost, and the shadow table is torn down in optimize_fts() where a paired
VACUUM reclaims the space. CJK/substring search falls back to LIKE — the same
graceful path taken when the trigram tokenizer is unavailable.

The trigger-repair heuristic now expects only the triggers the active
configuration requires, so a deliberately-absent trigram index no longer
forces a full FTS rebuild on every open.
@alt-glitch alt-glitch added type/feature New feature or request comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint area/config Config system, migrations, profiles P3 Low — cosmetic, nice to have sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 21, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for preserving the config.yaml approach and for documenting the storage rationale. The underlying opt-out is still absent on current main, so this is not redundant.

Problems

  • This patch predates the v23 FTS redesign. Current main creates trigram indexes in both schema paths at hermes_state_schema.py:689-725, and optimize_fts_storage() recreates them while migrating legacy databases at hermes_state_search.py:393-438. The implementation needs a port across those current paths rather than a cherry-pick of the old hermes_state.py edits.
  • Current main independently enables the CJK-bigram index (hermes_cli/config_defaults.py:2579-2586; initialization at hermes_state_schema.py:726-728). The proposed LIKE-fallback documentation needs an explicit contract for that index.
  • The new tests monkeypatch _trigram_fts_enabled, so they do not verify the YAML/config-loader path described by the feature.

Suggested changes

  • Rework against the current schema and optimize-storage lifecycle, then add temp-HERMES_HOME YAML E2E coverage for default, disable, and re-enable behavior.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/sessions Session lifecycle, resume, persistence, history labels Jul 30, 2026
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

Eleven PRs address or reference the FTS storage complex: #20239 and merged #65798 replace inline indexes with external-content layouts; #35826, #43701, and #65798 reduce trigram indexing of tool data; and #22710, #27770, #42190, #45916, #57761, #68089, and #68394 add variants of a trigram opt-out. The merged #65798 addresses the principal duplication and tool-row amplification causes, while the open #68394 targets the still-absent operator opt-out but predates the resulting v23 architecture.

Related pull requests

Duplicates

#22710 and #27770 are the same patch lineage; #42190 and #45916 substantially duplicate that HERMES_DISABLE_FTS_TRIGRAM approach, while #57761 implements the same opt-out under another environment variable. #68089 and #68394 are competing config.yaml-based successors; #20239, #35826, and #43701 supplied storage-layout or tool-indexing changes later consolidated in #65798.

Suggested consolidation

Author action on #68394: rebase onto current main, or split out the config-based opt-out and port it across hermes_state_schema.py:689-728 and hermes_state_search.py:393-438, including the optimize-storage lifecycle, an explicit CJK-bigram contract, and temp-HERMES_HOME YAML tests for default, disable, and re-enable. This follows the keep_open salvage review rather than bypassing it; the closed environment-variable duplicates #22710, #27770, #42190, #45916, and #57761 remain closed, #20239/#35826/#43701 are superseded by merged #65798, and #68089 can remain closed as the competing config-based predecessor.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I22478(["issue #22478 (closed)"])
    I43690(["issue #43690 (closed)"])
    I55233(["issue #55233 (closed)"])
    subgraph Dup68089 ["PRs duplicating each other"]
        P68089["PR #68089 (closed)"]
        P68394["PR #68394 (open)"]
    end
    P68394 -.->|partial| I22478
    P68394 -.->|partial| I43690
    P68394 -->|fixes| I55233
    class I22478 closed
    class I43690 closed
    class I55233 closed
    class P68089 closed
    class P68394 open
    class P68394 target
    click I22478 "https://github.com/NousResearch/hermes-agent/issues/22478"
    click I43690 "https://github.com/NousResearch/hermes-agent/issues/43690"
    click I55233 "https://github.com/NousResearch/hermes-agent/issues/55233"
    click P68089 "https://github.com/NousResearch/hermes-agent/pull/68089"
    click P68394 "https://github.com/NousResearch/hermes-agent/pull/68394"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 11 pull requests and 3 issues in this complex. Diffs were read for 10 of 11 PRs (rest unavailable); Assessment working set: 249 kB of PR diffs, 53 kB of issue/PR text, 37 kB of discussion (35 comments), 39 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles area/sessions Session lifecycle, resume, persistence, history comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add config flag to disable trigram FTS index (prevent state.db bloat)

4 participants