hermes - 💡(How to fix) Fix Holographic memory: `fact_store action="search"` silently returns 0 results when query contains FTS5 operator characters (e.g. hyphens)

Official PRs (…)
ON THIS PAGE

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…

The Holographic memory provider's fact_store(action="search", query=...) tool passes the user-supplied query string straight through to SQLite FTS5 MATCH ? with no sanitisation. Because FTS5 interprets several characters as operators — most commonly - (NOT) and : (column-restricted match) — common identifier-shaped queries (markers, UUIDs, ISO timestamps, GUIDs, hostnames) either match the wrong rows or trigger a parse error.

The parse error is then silently swallowed by a bare except Exception: return [], so the calling agent sees zero results with no diagnostic, and the user has no signal that the query was malformed.

Error Message

sqlite> SELECT COUNT(*) FROM facts_fts WHERE facts_fts MATCH 'holographic-restart-test-7AF3'; Error: stepping, no such column: restart

Root Cause

The Holographic memory provider's fact_store(action="search", query=...) tool passes the user-supplied query string straight through to SQLite FTS5 MATCH ? with no sanitisation. Because FTS5 interprets several characters as operators — most commonly - (NOT) and : (column-restricted match) — common identifier-shaped queries (markers, UUIDs, ISO timestamps, GUIDs, hostnames) either match the wrong rows or trigger a parse error.

Fix Action

Fix / Workaround

Workaround in the meantime: agents can be instructed to strip non-alphanumeric characters before calling fact_store(action="search", ...), or to use action="list" plus client-side filtering for marker-shaped lookups.

Code Example

from plugins.memory.holographic import HolographicMemoryProvider

p = HolographicMemoryProvider()
p.initialize(session_id="repro")

# 1. Add a fact with a hyphenated marker (very common analyst pattern).
p.handle_tool_call("fact_store", {
    "action": "add",
    "content": "Briefing window: Thursday 14:00 UTC. Marker holographic-restart-test-7AF3.",
    "tags": "holographic-restart-test-7AF3,briefing",
})

# 2. Confirm via list — fact IS in the database.
print(p.handle_tool_call("fact_store", {"action": "list", "limit": 5}))
# {"facts": [{"fact_id": 1, "content": "Briefing window: ...", ...}], "count": 1}

# 3. Now search for the marker. Returns zero.
print(p.handle_tool_call("fact_store", {
    "action": "search",
    "query": "holographic-restart-test-7AF3",
}))
# {"results": [], "count": 0}

---

sqlite> SELECT COUNT(*) FROM facts_fts WHERE facts_fts MATCH 'holographic-restart-test-7AF3';
Error: stepping, no such column: restart
RAW_BUFFERClick to expand / collapse

Holographic memory: fact_store action="search" silently returns 0 results when query contains FTS5 operator characters

Summary

The Holographic memory provider's fact_store(action="search", query=...) tool passes the user-supplied query string straight through to SQLite FTS5 MATCH ? with no sanitisation. Because FTS5 interprets several characters as operators — most commonly - (NOT) and : (column-restricted match) — common identifier-shaped queries (markers, UUIDs, ISO timestamps, GUIDs, hostnames) either match the wrong rows or trigger a parse error.

The parse error is then silently swallowed by a bare except Exception: return [], so the calling agent sees zero results with no diagnostic, and the user has no signal that the query was malformed.

Affected files

  • plugins/memory/holographic/retrieval.py, method FactRetriever._fts_candidates (currently around line 481)

Reproducer

Against plugins/memory/holographic/__init__.py v0 ([master HEAD], also seen in current main):

from plugins.memory.holographic import HolographicMemoryProvider

p = HolographicMemoryProvider()
p.initialize(session_id="repro")

# 1. Add a fact with a hyphenated marker (very common analyst pattern).
p.handle_tool_call("fact_store", {
    "action": "add",
    "content": "Briefing window: Thursday 14:00 UTC. Marker holographic-restart-test-7AF3.",
    "tags": "holographic-restart-test-7AF3,briefing",
})

# 2. Confirm via list — fact IS in the database.
print(p.handle_tool_call("fact_store", {"action": "list", "limit": 5}))
# {"facts": [{"fact_id": 1, "content": "Briefing window: ...", ...}], "count": 1}

# 3. Now search for the marker. Returns zero.
print(p.handle_tool_call("fact_store", {
    "action": "search",
    "query": "holographic-restart-test-7AF3",
}))
# {"results": [], "count": 0}

Direct SQL probe against the same memory_store.db confirms what FTS5 is actually doing:

sqlite> SELECT COUNT(*) FROM facts_fts WHERE facts_fts MATCH 'holographic-restart-test-7AF3';
Error: stepping, no such column: restart

The - characters are interpreted as NOT operators, splitting the query into holographic NOT restart NOT test NOT 7AF3. SQLite's FTS5 parser then errors on the column-vs-token ambiguity (restart: shape) and the application catches the exception and returns [].

fact_store(action="search", query="holographic restart test 7AF3") (same content, hyphens swapped for spaces) returns the row correctly. The data is fine; the query syntax is the problem.

Expected vs observed

ExpectedObserved
Behaviour for query with -Either tokenize on punctuation and AND/OR the resulting tokens, OR raise a clear error to the agent so it can retry with sanitised inputSilently returns 0 results, masking the bug as a "no such fact" outcome
Behaviour for FTS5 parse errorSurface the error so the caller can correct itCaught by except Exception: return [] with no logging

Impact

  • Any query containing a hyphenated identifier silently misses real data. Common shapes: UUIDs (a1b2c3-...), session IDs, marker tags (socal-runbook), CVE IDs (CVE-2024-12345), ISO timestamps (2026-05-07), hostnames (prod-bastion-01).
  • The HRR-only actions (probe, related, reason, contradict) all hard-filter WHERE hrr_vector IS NOT NULL, so they too return empty when numpy is not installed or when add_fact() skipped HRR encoding for some other reason — that's a separate but related "silent empty" surface.
  • Combined effect: an agent that writes a fact with a hyphenated tag and then searches for that same tag gets a truthful "fact persisted" response on add and a misleading "no results" response on search, with no log line that explains the discrepancy.

Suggested fix direction (for discussion only — not committing to a PR)

Two complementary changes inside _fts_candidates:

  1. Sanitise the query before passing to MATCH. Simplest robust approach: extract [A-Za-z0-9_]+ runs, lowercase them, and join with spaces (FTS5 default is implicit AND on space-separated tokens). For users who genuinely want phrase semantics, the call site can opt in via a new phrase=True kwarg that wraps the sanitised query in double quotes.
  2. Replace the bare except Exception: return [] with a logged warning (e.g. logger.warning("FTS5 MATCH failed for query=%r: %s", query, exc)) so malformed-query errors are visible at runtime instead of indistinguishable from genuine zero-result outcomes.

Workaround in the meantime: agents can be instructed to strip non-alphanumeric characters before calling fact_store(action="search", ...), or to use action="list" plus client-side filtering for marker-shaped lookups.

Environment

  • Hermes Agent: v2026.4.30 (v0.12.0)
  • Python: 3.13-slim base image
  • SQLite: ships with python:3.13-slim (sqlite3.sqlite_version ≈ 3.40.x)
  • Plugin: plugins/memory/holographic/ (in-tree, master at the time of report)
  • numpy: not installed (HRR weights auto-zeroed; FTS5 path is the only retrieval surface, which is exactly what the bug breaks)

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