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
|
Expected |
Observed |
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 input |
Silently returns 0 results, masking the bug as a "no such fact" outcome |
| Behaviour for FTS5 parse error |
Surface the error so the caller can correct it |
Caught 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:
- 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.
- 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)
Holographic memory:
fact_store action="search"silently returns 0 results when query contains FTS5 operator charactersSummary
The Holographic memory provider's
fact_store(action="search", query=...)tool passes the user-supplied query string straight through to SQLite FTS5MATCH ?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, methodFactRetriever._fts_candidates(currently around line 481)Reproducer
Against
plugins/memory/holographic/__init__.pyv0 ([master HEAD], also seen in currentmain):Direct SQL probe against the same
memory_store.dbconfirms what FTS5 is actually doing:The
-characters are interpreted as NOT operators, splitting the query intoholographic 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
-except Exception: return []with no loggingImpact
a1b2c3-...), session IDs, marker tags (socal-runbook), CVE IDs (CVE-2024-12345), ISO timestamps (2026-05-07), hostnames (prod-bastion-01).probe,related,reason,contradict) all hard-filterWHERE hrr_vector IS NOT NULL, so they too return empty when numpy is not installed or whenadd_fact()skipped HRR encoding for some other reason — that's a separate but related "silent empty" surface.addand a misleading "no results" response onsearch, 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:[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 newphrase=Truekwarg that wraps the sanitised query in double quotes.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 useaction="list"plus client-side filtering for marker-shaped lookups.Environment
sqlite3.sqlite_version≈ 3.40.x)plugins/memory/holographic/(in-tree, master at the time of report)