Skip to content

fix(state): project multimodal text into FTS indexes - #69798

Open
konsisumer wants to merge 1 commit into
NousResearch:mainfrom
konsisumer:fix/state-fts-multimodal-projection
Open

fix(state): project multimodal text into FTS indexes#69798
konsisumer wants to merge 1 commit into
NousResearch:mainfrom
konsisumer:fix/state-fts-multimodal-projection

Conversation

@konsisumer

Copy link
Copy Markdown
Contributor

What does this PR do?

Projects sentinel-prefixed multimodal message content into an FTS-only text field, so trigram and CJK indexes include text parts but exclude image URLs and base64 payloads. This removes the embedded NUL that makes trigram integrity checks SQLite-version-dependent, while preserving the original stored content for replay. Existing external-content indexes are upgraded deliberately by hermes sessions optimize-storage.

Related Issue

Fixes #69672

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • hermes_state.py: add a SessionDB-maintained multimodal text projection, use it in trigram/CJK FTS views, triggers, and rebuild paths, and version the opt-in FTS upgrade.
  • hermes_cli/main.py: advertise the projection rebuild and describe the updated optimize-storage behavior.
  • tests/test_hermes_state.py: cover text-only multimodal indexing, image-payload exclusion, FTS integrity, and rebuilding the prior external-content layout.

How to Test

  1. Activate the repository virtualenv and run /opt/homebrew/bin/timeout -k 30 480 sh -c 'pytest tests/ -q -x --timeout=60 "$@"' sh.
  2. Run /opt/homebrew/bin/timeout -k 30 480 $VIRTUAL_ENV/bin/python -m pytest tests/test_hermes_state.py tests/test_fts_cjk_bigram.py -q -x --timeout=60.
  3. Create a session with a text plus image_url multimodal message; confirm the text matches messages_fts_trigram but the base64 token does not, then run the FTS integrity-check.
  4. For an existing affected database, run hermes sessions optimize-storage and verify PRAGMA quick_check succeeds.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS (Darwin 25.5.0, arm64)

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

For New Skills

N/A — no skill is added.

Screenshots / Logs

N/A — database/index behavior only.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard area/sessions Session lifecycle, resume, persistence, history sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 23, 2026
@konsisumer
konsisumer force-pushed the fix/state-fts-multimodal-projection branch from 7d2bcae to c2a7e67 Compare July 24, 2026 07:50
@konsisumer

Copy link
Copy Markdown
Contributor Author

Rebased onto current main and resolved the session-state conflicts by retaining both the FTS text projection and upstream display metadata in the shared write paths.

Validation: python -m pytest tests/test_hermes_state.py tests/test_fts_cjk_bigram.py -q -x --timeout=60 — 430 passed. Ruff, diff whitespace, and the diff-scoped Windows-footgun check pass. The configured full suite cannot collect in this workspace because the system Python lacks optional fastapi/uvicorn.

@konsisumer

Copy link
Copy Markdown
Contributor Author

Rebased onto current main and resolved the session-state conflicts while retaining both the FTS text projection and upstream display-metadata handling.

Validation: the new multimodal FTS tests pass (2), the FTS migration class passes with the one known SQLite held-reader timeout excluded (11 passed; the timeout reproduces on origin/main), and display-metadata regression tests pass (33). Diff-scoped Ruff and whitespace checks pass. The configured full suite cannot collect locally because the system Python lacks optional FastAPI/Uvicorn.

@konsisumer
konsisumer force-pushed the fix/state-fts-multimodal-projection branch from c2a7e67 to 72a41a1 Compare July 25, 2026 07:19
@konsisumer

Copy link
Copy Markdown
Contributor Author

Rebased onto current main, resolving a test-only append conflict while retaining both the multimodal FTS coverage and upstream gateway-routing regression tests.

Also corrected 12 existing Path.write_text() encoding diagnostics in tests/test_hermes_state.py, required by the blocking Windows-footguns check.

Validation: 8 affected state tests passed; scoped Ruff, the scoped Windows-footguns check, and git diff --check pass. The configured full suite cannot collect in this workspace because its system Python lacks FastAPI/Uvicorn.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tracing the raw multimodal payload through both substring indexes; current main still has that behavior in hermes_state_common.py:391-445 and hermes_state.py:1419-1475.

Problems

  • hermes_state.py:3254-3255 calls executescript() inside _execute_write(). Current main explicitly records that CJK executescript() calls implicitly commit and must run outside _execute_write's BEGIN IMMEDIATE transaction (hermes_state_search.py:350-357). The CJK projection upgrade must preserve that transaction boundary.
  • The new coverage exercises trigram only (tests/test_hermes_state.py:6256+); it does not exercise the new CJK upgrade branch at hermes_state.py:3241-3269.

Suggested changes

  • Port the work to the split current-main modules and recreate the CJK schema outside _execute_write, following hermes_state_search.py:350-357.
  • Add a v1 CJK projection-upgrade regression test including text, an image payload, and an FTS integrity check.

Automated hermes-sweeper review.

Comment thread hermes_state.py
conn.execute(f"DROP TRIGGER IF EXISTS {trigger}")
conn.execute("DROP VIEW IF EXISTS messages_fts_cjk_src")
self._backfill_fts_content(conn)
conn.executescript(FTS_CJK_TABLE_SQL)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

executescript() implicitly commits, so this cannot safely run inside _execute_write()'s BEGIN IMMEDIATE transaction. Current main documents this exact constraint in the CJK stale-reset path. Recreate the CJK schema outside _execute_write (or use transaction-safe individual statements) and add coverage for this projection-upgrade path.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 30, 2026
@konsisumer

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback — I looked into this, but it can't be addressed on this PR right now:

The requested review repair must change hermes_state_common.py and hermes_state_search.py after their current-main split, but those files are outside this PR's original three-file scope.

@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

One PR addresses Issue #69672. #69798 adds a text-only multimodal projection across FTS write, view, trigger, and rebuild paths, removing the NUL-prefixed JSON and image payloads that cause SQLite-version-dependent integrity failures and index bloat, but its CJK upgrade path conflicts with the transaction boundary documented on current main.

Related pull requests

  • fix(state): project multimodal text into FTS indexes #69798 best fix — (+409/-90) — n/a: The diff preserves raw multimodal content for replay while indexing only projected text and adds trigram integrity and v1-upgrade tests. The contributor's COMMENTED keep_open review identifies required follow-up: port the implementation to the current split modules, move CJK schema recreation outside _execute_write() because executescript() implicitly commits, and add a v1 CJK projection-upgrade regression test.

Suggested consolidation

Author action: rebase #69798 onto main, or split out the part that can merge. Consistent with the keep_open review on #69798, retain the text-projection approach, but first preserve the CJK transaction boundary described at hermes_state_search.py:350-357 and add coverage for the CJK upgrade branch; there are no duplicate PRs to close.

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
    I69672(["issue #69672 (open)"])
    P69798["PR #69798 (open)"]
    P69798 -->|best fix| I69672
    class I69672 open
    class P69798 open
    class P69798 best
    class P69798 target
    click I69672 "https://github.com/NousResearch/hermes-agent/issues/69672"
    click P69798 "https://github.com/NousResearch/hermes-agent/pull/69798"
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 1 pull request and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 43 kB of PR diffs, 9 kB of issue/PR text, 4 kB of discussion (6 comments), 3 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

@konsisumer

Copy link
Copy Markdown
Contributor Author

The triage summary matches the outstanding review feedback. This repair still cannot be made on this PR: the current-main implementation requires changes to hermes_state_common.py and hermes_state_search.py, which are outside this PR's original three-file scope. The PR remains parked pending an operator unblock or a separately scoped follow-up.

@konsisumer

Copy link
Copy Markdown
Contributor Author

Thanks for the production measurements — they confirm that the projection must apply to sentinel-prefixed multimodal rows regardless of role, including user-attached images; the existing role='tool' exclusion is not sufficient. The repeated-attachment finding is also useful, but content-addressed attachment storage is a separate persistence-model change from the FTS projection.

This still cannot be done on this PR because current main requires the repair in hermes_state_common.py and hermes_state_search.py, outside this PR's original three-file scope. The PR remains parked pending an operator unblock or a separately scoped follow-up.

@konsisumer

Copy link
Copy Markdown
Contributor Author

Thanks for the Windows runtime comparison. The same database producing different integrity results under SQLite 3.45.1 and 3.53.1, alongside the NUL-prefixed multimodal rows and reproducible rebuild flip, is strong cross-platform evidence for an FTS tokenization incompatibility rather than proven physical database damage. It also reinforces the need to keep the sentinel and image payloads out of the FTS projection.

This still cannot be done on this PR: after the current-main split, the outstanding repair requires changes to hermes_state_common.py and hermes_state_search.py, which are outside this PR's original three-file scope. The PR remains parked pending an operator unblock or a separately scoped follow-up.

ishangodawatta added a commit to ishangodawatta/hermes-agent that referenced this pull request Aug 17, 2026
The trigram and CJK-bigram indexes stored raw sentinel-prefixed
multimodal JSON (including base64 image payloads) verbatim. SQLite's
trigram tokenizer handles embedded NUL differently across versions, so
an index written by one SQLite build reads as a malformed inverted
index under another, and image payloads bloated the index with
unsearchable bytes.

Adds a SessionDB-maintained fts_content projection (text parts only,
image_url/base64 dropped) and reads it through the trigram/CJK content
views and every trigger/backfill/rebuild path, instead of raw content.
FTS_STORAGE_VERSION 1 -> 2; `hermes sessions optimize-storage` rebuilds
an older index onto the projection.

Completes NousResearch#69798, ported onto the hermes_state_common/_search/_schema
split it predates, and fixes the two review blockers: CJK schema
recreation now runs outside _execute_write()'s transaction (executescript
implicitly commits), and adds the requested CJK-projection-upgrade
regression test.

Fixes NousResearch#69672
@konsisumer

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback — I looked into this, but it can't be addressed on this PR right now:

Stale base with semantic conflicts: the original three-file PR diff is 409 insertions and 90 deletions, exceeding the safe re-apply cap; current main moves the FTS schema it must change into hermes_state_common.py, outside the original PR scope.

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

Labels

area/sessions Session lifecycle, resume, persistence, history comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists 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/bug Something isn't working

Projects

None yet

4 participants