Context
PR #389 (fix for #388) makes the FTS leg of hybrid search fail open: when a query contains residual FTS5 metacharacters that survive sanitization, the FTS leg degrades to empty and fusion continues vector-only, instead of the whole verb erroring.
The degradation is logged (tracing::warn!) but not visible to the caller on three surfaces, because their wire shape is a bare JSON array with no envelope to carry an advisory field:
search(kind="entity") — crates/khive-pack-kg/src/handlers/search.rs, returns Vec<Value>
search(kind="note") — same file, note branch, returns Vec<Value>
memory.recall default path — crates/khive-pack-memory/src/handlers/recall.rs, returns a bare array (only the rare verbose multi-model branch returns an object)
By contrast, knowledge.search could carry ann_unavailable (#335) only because its handler already returns an object envelope.
Why this is deferred
Adding an fts_degraded field to these three verbs means a breaking array-to-object wire-shape change, which is an ADR-023 surface-contract amendment, not a bug-fix change. ADR-029's partial: true / missing_backends advisory is specified (ADR-029 §result envelope) but not implemented anywhere today, so it is not a ready escape hatch either.
Options
- Migrate the three verbs to an object envelope (
{results: [...], fts_degraded?: true}) via an ADR-023 amendment + AGENTS.md update, with a deprecation window for array consumers.
- Implement ADR-029's envelope-level advisory injection at the MCP dispatch layer, so per-verb handlers stay array-shaped and the
request batch envelope carries per-op advisories.
- Accept log-only visibility as the contract for array-shaped verbs and document it.
Option 2 keeps handler shapes stable and matches where the batch envelope ({ok, tool, result}) already lives; it is the likely right seam, but it needs ADR-029 implementation work sized first.
Context
PR #389 (fix for #388) makes the FTS leg of hybrid search fail open: when a query contains residual FTS5 metacharacters that survive sanitization, the FTS leg degrades to empty and fusion continues vector-only, instead of the whole verb erroring.
The degradation is logged (
tracing::warn!) but not visible to the caller on three surfaces, because their wire shape is a bare JSON array with no envelope to carry an advisory field:search(kind="entity")—crates/khive-pack-kg/src/handlers/search.rs, returnsVec<Value>search(kind="note")— same file, note branch, returnsVec<Value>memory.recalldefault path —crates/khive-pack-memory/src/handlers/recall.rs, returns a bare array (only the rare verbose multi-model branch returns an object)By contrast,
knowledge.searchcould carryann_unavailable(#335) only because its handler already returns an object envelope.Why this is deferred
Adding an
fts_degradedfield to these three verbs means a breaking array-to-object wire-shape change, which is an ADR-023 surface-contract amendment, not a bug-fix change. ADR-029'spartial: true/missing_backendsadvisory is specified (ADR-029 §result envelope) but not implemented anywhere today, so it is not a ready escape hatch either.Options
{results: [...], fts_degraded?: true}) via an ADR-023 amendment + AGENTS.md update, with a deprecation window for array consumers.requestbatch envelope carries per-op advisories.Option 2 keeps handler shapes stable and matches where the batch envelope (
{ok, tool, result}) already lives; it is the likely right seam, but it needs ADR-029 implementation work sized first.