hermes - ✅(Solved) Fix [Bug]: fact_store search action fails with "no such column" error on hyphenated queries (FTS5 tokenization) [2 pull requests, 2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#14024Fetched 2026-04-23 07:47:18
View on GitHub
Comments
2
Participants
2
Timeline
7
Reactions
0
Author
Participants
Timeline (top)
labeled ×3commented ×2cross-referenced ×2

Found while debugging agent behavior on my homelab ( Holographic provider active). The agent was issuing 4 sequential fact_store calls for single-fact lookups, all returning 0 results with no visible error. Dropped to direct sqlite3 queries against ~/.hermes/profiles/<profile>/memory_store.db to isolate the cause. All repros below were executed manually against my actual database.

This report was drafted with AI assistance, but every query/error in the reproduction section was verified live. Happy to provide the full diagnostic session output if useful.

Error Message

Setup

fact_store(action="add", content="PVE-01 hardware: i5-13500T, IP 10.20.90.00", category="hardware", tags="pve-01,homelab")

Fails

fact_store(action="search", query="pve-01")

-> OperationalError: no such column: 01

-> Returns 0 results

Workarounds (all return the expected hit)

fact_store(action="search", query='"pve-01"') # phrase-quoted fact_store(action="search", query="pve 01") # space-separated AND

Root Cause

Root cause: the FTS5 virtual table is created without an explicit tokenizer, so it defaults to unicode61 which treats - as a separator. When the plugin passes the raw query string pve-01 directly to MATCH, FTS5's query parser interprets the hyphen as the column-restriction operator and tries to resolve 01 as a column name. Since no column named 01 exists, the query errors out with zero results.

Fix Action

Fix / Workaround

Workarounds (all return the expected hit)

fact_store(action="search", query='"pve-01"') # phrase-quoted fact_store(action="search", query="pve 01") # space-separated AND


## Workaround for users hitting this now

PR fix notes

PR #14033: fix(memory): sanitize holographic fact_store FTS queries

Description (problem / solution / changelog)

Summary

  • sanitize holographic fact_store(action="search") FTS5 queries before they reach MATCH
  • quote bare hyphenated and dotted tokens so hostnames like pve-01 stop triggering SQLite parse errors
  • add regression coverage for both the raw store search path and the higher-level retriever path

Problem

The holographic memory provider passed raw user queries directly into facts_fts MATCH ?.

For hyphenated identifiers such as pve-01, SQLite FTS5 parses the hyphen as query syntax and raises:

OperationalError: no such column: 01

In practice this broke fact retrieval in two ways:

  • MemoryStore.search_facts() raised immediately
  • FactRetriever.search() swallowed the FTS error and returned an empty result set, making the failure look like a simple miss

Fix

This PR adds a small _sanitize_fts5_query() helper in the holographic store and uses it in both FTS entry points:

  • MemoryStore.search_facts()
  • FactRetriever._fts_candidates()

The sanitizer mirrors Hermes' existing SessionDB FTS5 handling:

  • preserve balanced quoted phrases
  • strip malformed FTS5-special characters/operators
  • wrap bare dotted/hyphenated tokens in double quotes

That keeps holographic memory search aligned with the project's existing transcript-search behavior while fixing the concrete pve-01 regression from #14024.

Tests

  • pytest -o addopts="" tests/plugins/memory/test_holographic_provider.py

Manual verification

store.search_facts("pve-01", category="hardware", limit=10)
retriever.search("pve-01", category="hardware", limit=10)

Both now return the stored PVE-01 ... fact instead of raising / silently returning [].

Closes #14024

Changed files

  • plugins/memory/holographic/retrieval.py (modified, +3/-1)
  • plugins/memory/holographic/store.py (modified, +27/-1)
  • tests/plugins/memory/test_holographic_provider.py (added, +38/-0)

PR #14262: fix(memory/holographic): sanitize hyphenated tokens in FTS5 queries

Description (problem / solution / changelog)

Fixes 'no such column' errors when fact_store search receives hyphenated identifiers like 'pve-01' by wrapping bare hyphenated tokens in double quotes before passing to FTS5 MATCH.

Closes #14024

Changed files

  • agent/error_classifier.py (modified, +8/-0)
  • gateway/run.py (modified, +1/-1)
  • plugins/memory/holographic/retrieval.py (modified, +15/-1)

Code Example

# Setup
fact_store(action="add",
    content="PVE-01 hardware: i5-13500T, IP 10.20.90.00",
    category="hardware", tags="pve-01,homelab")

# Fails
fact_store(action="search", query="pve-01")
# -> OperationalError: no such column: 01
# -> Returns 0 results

# Workarounds (all return the expected hit)
fact_store(action="search", query='"pve-01"')   # phrase-quoted
fact_store(action="search", query="pve 01")     # space-separated AND

---

import sqlite3
c = sqlite3.connect("~/.hermes/profiles/<profile>/memory_store.db")

# These error:
c.execute("SELECT COUNT(*) FROM facts_fts WHERE facts_fts MATCH ?", ("pve-01",))
# sqlite3.OperationalError: no such column: 01

c.execute("SELECT COUNT(*) FROM facts_fts WHERE facts_fts MATCH ?", ("PVE-01",))
# sqlite3.OperationalError: no such column: 01

# These work:
c.execute("SELECT COUNT(*) FROM facts_fts WHERE facts_fts MATCH ?", ('"pve-01"',))
# -> returns hits

c.execute("SELECT COUNT(*) FROM facts_fts WHERE facts_fts MATCH ?", ("pve 01",))
# -> returns hits

---

CREATE VIRTUAL TABLE facts_fts
    USING fts5(content, tags, content=facts, content_rowid=fact_id);

---

import re

def sanitize_fts5_query(query: str) -> str:
    # Wrap bare hyphenated tokens in double quotes
    return re.sub(r'(?<!")(\b\w+(?:-\w+)+\b)(?!")', r'"\1"', query)

---

fact_store(action="search", query='"pve-01"')
RAW_BUFFERClick to expand / collapse

Context

Found while debugging agent behavior on my homelab ( Holographic provider active). The agent was issuing 4 sequential fact_store calls for single-fact lookups, all returning 0 results with no visible error. Dropped to direct sqlite3 queries against ~/.hermes/profiles/<profile>/memory_store.db to isolate the cause. All repros below were executed manually against my actual database.

This report was drafted with AI assistance, but every query/error in the reproduction section was verified live. Happy to provide the full diagnostic session output if useful.

Bug Description

The Holographic memory provider's fact_store(action="search", ...) raises a SQLite OperationalError: no such column: <N> when the query string contains a hyphenated token like pve-01, pihole-02, lxc-103, etc.

Root cause: the FTS5 virtual table is created without an explicit tokenizer, so it defaults to unicode61 which treats - as a separator. When the plugin passes the raw query string pve-01 directly to MATCH, FTS5's query parser interprets the hyphen as the column-restriction operator and tries to resolve 01 as a column name. Since no column named 01 exists, the query errors out with zero results.

This is particularly impactful for homelab/infrastructure users whose hostnames commonly follow <name>-<number> naming conventions.

Reproduction

Environment: Hermes Agent, Holographic memory provider active, any profile.

Create a fact with a hyphenated identifier, then try to search for it:

# Setup
fact_store(action="add",
    content="PVE-01 hardware: i5-13500T, IP 10.20.90.00",
    category="hardware", tags="pve-01,homelab")

# Fails
fact_store(action="search", query="pve-01")
# -> OperationalError: no such column: 01
# -> Returns 0 results

# Workarounds (all return the expected hit)
fact_store(action="search", query='"pve-01"')   # phrase-quoted
fact_store(action="search", query="pve 01")     # space-separated AND

Direct SQLite reproduction:

import sqlite3
c = sqlite3.connect("~/.hermes/profiles/<profile>/memory_store.db")

# These error:
c.execute("SELECT COUNT(*) FROM facts_fts WHERE facts_fts MATCH ?", ("pve-01",))
# sqlite3.OperationalError: no such column: 01

c.execute("SELECT COUNT(*) FROM facts_fts WHERE facts_fts MATCH ?", ("PVE-01",))
# sqlite3.OperationalError: no such column: 01

# These work:
c.execute("SELECT COUNT(*) FROM facts_fts WHERE facts_fts MATCH ?", ('"pve-01"',))
# -> returns hits

c.execute("SELECT COUNT(*) FROM facts_fts WHERE facts_fts MATCH ?", ("pve 01",))
# -> returns hits

Schema for reference

CREATE VIRTUAL TABLE facts_fts
    USING fts5(content, tags, content=facts, content_rowid=fact_id);

No tokenize= clause, so default unicode61 is used.

Impact

  • Silent retrieval failure — users don't see the error, they just get {"results": [], "count": 0} and assume the fact isn't there.
  • Agent LLMs compensate by issuing multiple alternative queries (observed: 4 sequential fact_store calls for a single lookup), burning tokens and latency.
  • Affects any deployment with hyphenated naming (homelab/infra, K8s resources, container names, Docker compose services, etc.).

Proposed fix (pick one)

Option A — Escape in plugin (minimally invasive, backward compatible):

In the fact_store search action, auto-wrap unquoted hyphenated tokens in double quotes before passing to FTS5:

import re

def sanitize_fts5_query(query: str) -> str:
    # Wrap bare hyphenated tokens in double quotes
    return re.sub(r'(?<!")(\b\w+(?:-\w+)+\b)(?!")', r'"\1"', query)

Option B — Change tokenizer (schema migration):

Recreate facts_fts with tokenize="unicode61 tokenchars '-_.'" so hyphens become part of tokens. Requires rebuilding the index for existing users.

Option A is backward-compatible and doesn't require migration. Option B gives better matching semantics long-term.

Workaround for users hitting this now

Update your retrieval protocol (in MEMORY.md or agent instructions) to always wrap hyphenated search terms in double quotes:

fact_store(action="search", query='"pve-01"')

Environment

  • Hermes Agent (main branch as of filing)
  • Holographic memory provider (plugins/memory/holographic)
  • Python 3.x, SQLite with FTS5 support
  • Tokenizer: default unicode61 (no tokenize= clause in CREATE VIRTUAL TABLE)

Related

  • Holographic plugin README lists fact_store search as a primary action but doesn't mention the hyphen limitation.
  • User-guide docs at /docs/user-guide/features/memory-providers similarly don't flag this.

extent analysis

TL;DR

To fix the issue with the Holographic memory provider's fact_store search action, you can either escape hyphenated tokens in the plugin or change the tokenizer in the FTS5 virtual table.

Guidance

  • The root cause of the issue is the default unicode61 tokenizer treating hyphens as separators, causing the FTS5 query parser to interpret the hyphen as a column-restriction operator.
  • To verify the issue, try searching for a fact with a hyphenated identifier using the fact_store action, and check if it returns an OperationalError: no such column error.
  • To mitigate the issue, you can wrap hyphenated search terms in double quotes, like this: fact_store(action="search", query='"pve-01"').
  • Another possible solution is to recreate the facts_fts virtual table with a custom tokenizer that treats hyphens as part of tokens, like this: CREATE VIRTUAL TABLE facts_fts USING fts5(content, tags, content=facts, content_rowid=fact_id, tokenize="unicode61 tokenchars '-_.'" );.

Example

You can use the sanitize_fts5_query function to automatically wrap bare hyphenated tokens in double quotes:

import re

def sanitize_fts5_query(query: str) -> str:
    return re.sub(r'(?<!")(\b\w+(?:-\w+)+\b)(?!")', r'"\1"', query)

Then, use this function to sanitize the query before passing it to the fact_store action:

query = "pve-01"
sanitized_query = sanitize_fts5_query(query)
fact_store(action="search", query=sanitized_query)

Notes

The proposed fix options have different trade-offs: Option A is minimally invasive and backward-compatible, while Option B provides better matching semantics but requires a schema migration.

Recommendation

Apply the workaround by wrapping hyphenated search terms in double quotes, as it is a simple and non-invasive solution that can be implemented immediately.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING