IBEX Harness
DocsBenchmarksBlogChangelogRoadmap
GitHub
IBEX Harness

Documentation

Architecture Decision RecordsADR-0002: Repository foundation bootstrapADR-0003: Branch protection and merge policyADR-0004: Protobuf and code generation policyADR-0005: Postgres migration strategyADR-0006: Auth protobuf contract (`ibex.auth.v1`)ADR-0007: Auth token validation implementationADR-0008: Security scanning and CI quality gatesADR-0009: Permission bitmap layoutADR-0010: Cryptography policyADR-0011: Proxy auth gRPC client and middlewareADR-0012: Proxy request normalization (OpenAI chat)ADR-0013: Proxy input validation and stable error envelopeADR-0014: Core domain migration sequencingADR-0015: Proxy rate limit skeleton (Phase 1)ADR-0016: Proxy agent identity verification (Phase 1)ADR-0017: Request ID and trace context strategy (Phase 1)ADR-0018: Graceful shutdown contract (Phase 1)ADR-0019: OpenTelemetry provider configuration (Phase 1)ADR-0020: Shared package boundaries — `packages/config` and `packages/apierror`ADR-0021: Prometheus Metric Catalog (Phase 1)ADR-0022: Health check contract (Phase 1)ADR-0023: Docs site architecture (Phase 1.5)ADR-0024: Benchmark data publishing modelADR-0025: LLM provider abstractionADR-0026: OpenAI client designADR-0027: Streaming dual-write strategyADR-0028: Auth cache designADR-0029: Token revocation propagation via Redis pub/subADR-0030: Directive versioning strategyADR-0031: System prompt injection strategyADR-0032: Session data model and retentionADR-0033: ClickHouse llm_traces schema and retentionADR-0034: Proxy overhead performance measurement methodologyADR-0035: Chat Idempotency-Key Redis dedupeADR-0038: Context assembly service design and gRPC contractADR-0039: Proxy Postgres ownership for session and directive storesADR-0040: Anthropic provider adapterADR-0041: Model capability registryADR-0042: Self-hosted OpenAI-compatible LLM adapterADR-0043: Tokenizer registry architectureADR-0044: Non-streaming response pipelineADR-0045: Streaming response transformationADR-0046: Embedder interface and profile registryADR-0047: Memory temporal validity foundationADR-0048: Memory multi-label categoriesADR-0049: Memory relationship graph readinessADR-0050: MCP server skeleton (transport, auth, audit)ADR-0051: Local LGTM observability stack (Phase 2.5 exit pull-forward)ADR-0052: Memory schema v2 expand (HNSW, quality columns)ADR-0053: Vector store abstraction and composite scoring v2ADR-0054: In-process Presidio PII detection in memoryADR-0055: Memory write-path exact and near-duplicate dedupADR-0056: Temporal-interval-aware conflict detectionADR-0057: Memory write-path persistence and orchestrationADR-0058: Semantic search FTS fallback triggerADR-0059: Hot cache read pathADR-0060: Memory service tenant isolation architectureADR-0061: Retrieval-quality benchmark methodologyADR-0062: Worker task observability and dead-letter handlingADR-0063: Extraction output schema v2ADR-0064: Extraction provider registry and batch executionADR-0065: Extraction batch idempotency and turn pointer orderingADR-0066: Extraction quality evaluation harnessADR-0067: Context capability catalog generate-and-diff for token budgetsADR-0068: Composite scoring-time relevance gateADR-0069: Context packer bounded DP knapsackADR-0070: Context formatting locked ordering and session nonceADR-0071: Context assembly gRPC degradation ladder and retrieval deadlineADR-0072: Extraction enqueue via worker HTTP (not Celery Redis wire format)ADR-0073: Organization Lifecycle and Cascading SuspensionADR-0074 Provider Credential StorageADR-0075 Per-Org Model Routing PolicyADR-0076: Circuit Breaker per ProviderADR-0077: Provider Fallback RoutingADR-0078: Operator runtime topology & environment contract (4.P.0)ADR-0079: Operator session RS256 dual-verify and TOTP step-upIBEX Console service rename and boundaryADR-0081: Fail-closed Proxy runtime controlsD2 metadata-only trace read contractD2 publication, identity, query, and lifecycle contract
ADRs›ADR-0058: Semantic search FTS fallback trigger
ADRs

ADR-0058: Semantic search FTS fallback trigger

Architecture decision record 0058 — when GIN full-text supplements vector search in find_similar, merge rules, and latency tradeoffs.

ADR-0058: Semantic search FTS fallback trigger

  • Status: Accepted
  • Date: 2026-08-29
  • Authors: IBEX Harness team
  • Milestone: 3.D.1 Semantic search read path

Context

Milestone 3.D.1 ships MemoryReadRepository.find_similar() with HNSW vector search as the primary path and GIN ts_rank_cd full-text search as a supplement when vector results do not fill the caller's requested page.

Phase 3 goals describe fallback when "vector results < K". During implementation review, a fixed sparse threshold (decoupled from caller limit) was considered to avoid running FTS when an agent's corpus is smaller than the requested page size.

Options Considered

1) Fixed sparse threshold (e.g. supplement only when len(vector_results) < 3)

Pros: Avoids FTS on agents whose corpus is smaller than limit but still semantically well-covered by vector hits.

Cons: Misses min_confidence-driven sparsity (vector ANN returns candidates that hydrate filters out). Fires FTS when vector already returned ≥ limit hits but a fixed threshold is low. Adds a magic constant unrelated to caller intent.

2) Caller-relative trigger: len(hydrated_vector_results) < limit (chosen)

Pros: "Sparse" means the caller's page is unfilled — regardless of whether the shortfall is due to corpus size, min_similarity filtering, or min_confidence dropping rows during hydrate. No magic constant. Matches API contract: return up to limit best results.

Cons: When limit exceeds corpus size (e.g. limit=20, 15 memories), FTS runs on every search for that agent. Measured FTS overhead at integration scale is small (see Consequences).

Decision

  1. Trigger: settings.search_fallback_enabled AND is_searchable_query(query_text) AND len(hydrated_vector_results) < limit.
  2. No fixed VECTOR_FALLBACK_THRESHOLD constant — the milestone sketch constant was illustrative, not a design requirement.
  3. Merge: Dedupe by memory_id (vector wins). Hydrate scoring fields, apply composite_score(), sort descending, cap at limit. HTTP similarity remains the retrieval metric (cosine or raw ts_rank_cd); ordering reflects composite. FTS supplemental hits use relevance sentinel 0.5 for composite scoring only (3.D.2).
  4. Metric: ibex_memory_search_fallback_total{triggered="true"} increments only when FTS returns at least one new candidate (not on every trigger evaluation).
  5. Toggle: IBEX_MEMORY_SEARCH_FALLBACK_ENABLED (default true).

Index verification (Semgrep relocation)

  • HNSW runtime gate: PgVectorStore.search + pg_stat idx_scan on idx_memories_embedding_hnsw (test_hnsw_index_scan_at_runtime). EXPLAIN helpers remain in benchmarks/memory/plan_explain.py for hnsw_bench only.
  • GIN verification: test-only probe transaction that drops competing partial btree indexes (restored on rollback), disables RLS, and applies planner hints (explain_gin_probe_plan + assert_gin_index_used) plus production full_text_search() hit count in test_gin_index_used_at_runtime. Partial btree indexes sharing the probe predicate otherwise route @@ through heap filters without exercising the GIN index.

Consequences

Frequency (typical agents)

ScenariolimitConditionFTS fires?
Default agent1015 memories, all pass filtersNo (10 vector hits)
Small agent108 memoriesYes (8 < 10)
Large limit2015 memoriesYes (15 < 20)
Full page3≥3 vector hits after hydrateNo
Confidence filter1012 memories, 5 pass min_confidenceYes (5 < 10)

With default limit=10, agents holding 10–30 memories do not run FTS on every search unless filters shrink results below limit.

Latency

Phase 3 budgets (latency table):

OperationBudget
pgvector HNSW searchp95 < 30ms @ 1M vectors
Memory write (full pipeline)p95 < 200ms

Operational alert (DEPLOYMENT.md): memory search p95 > 100ms for 10 minutes.

Integration smoke test (test_find_similar_latency_smoke) seeds 500 rows and asserts vector-only find_similar p95 < 100ms on CI hardware (not 1M scale). FTS supplement adds a small delta measured in the same test; the hard gate is vector-only p95.

3.D.2 (shipped)

Search ranking uses composite_score() after merge/dedupe and hydrate in find_similar() (app/read/ranking.py). FTS supplemental rows use fixed relevance 0.5 for the composite relevance component only; response similarity is unchanged.

Related

  • ADR-0053 — VectorStore ABC and HNSW SQL
  • ADR-0057 — write path (separate broad-exception cleanup tracked in GitHub #634)

Was this page helpful?

Edit on GitHub

Last updated on

PreviousADR-0057: Memory write-path persistence and orchestrationNextADR-0059: Hot cache read path

On this page

  • Context
  • Options Considered
  • 1) Fixed sparse threshold (e.g. supplement only when len(vector_results) < 3)
  • 2) Caller-relative trigger: len(hydrated_vector_results) < limit (chosen)
  • Decision
  • Index verification (Semgrep relocation)
  • Consequences
  • Frequency (typical agents)
  • Latency
  • 3.D.2 (shipped)
  • Related
0%