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-0068: Composite scoring-time relevance gate
ADRs

ADR-0068: Composite scoring-time relevance gate

Architecture decision record 0068 — exclude candidates below a configurable relevance floor before composite_score on the memory read path; distinct from ADR-0053 retrieval min_similarity.

ADR-0068: Composite scoring-time relevance gate

  • Status: Accepted
  • Date: 2026-09-05
  • Authors: IBEX Harness team
  • Milestone: 3.5.C.3 Composite Scorer v2 — Relevance-Gated
  • Depends on: ADR-0053 (retrieval min_similarity + composite weights / half-lives via 3.D.2)
  • Supersedes sketch: Milestone planning text that proposed ADR-0040 rewrite and a services/context MemoryScorer — ADR-0040 remains Anthropic provider adapter; scoring stays in memory.

Context

Composite ranking (composite_score) is a pure weighted sum over relevance, recency, usefulness, confidence, and access frequency (weights 0.40 / 0.25 / 0.20 / 0.10 / 0.05). Without a scoring-time gate, a near-zero relevance candidate with high non-relevance components can outrank a highly relevant one when retrieval is opened below the production ANN cutoff (tests and some callers set min_similarity=0.0).

ADR-0053 already defines a retrieval-time cosine cutoff (vector_search_min_similarity, default 0.70) applied in pgvector before hydrate. That does not replace a scoring-time floor:

MechanismStageInputDefault
min_similarity (ADR-0053)ANN searchcosine similarity0.70
composite_relevance_floor (this ADR)After hydrate, before composite_scorecomposite relevance component (vector cosine or FTS sentinel 0.5)0.15

Category-conditional half-lives (CATEGORY_HALF_LIFE_DAYS) and formula weights shipped in 3.D.2 and are out of scope here.

Decision

1) Gate in memory read ranking, not in pure score()

  • Helper: passes_relevance_floor(relevance, floor) in services/memory/app/scoring/relevance_gate.py
  • Applied in rank_hydrated_hits(..., relevance_floor=...) before calling composite_score
  • MemoryReadRepository.find_similar passes Settings.composite_relevance_floor
  • Write-path hot-cache scoring (relevance=1.0) is unaffected
  • score() / composite_score() remain pure weighted sums (existing unit contracts unchanged)

2) Configurable floor

  • Setting: composite_relevance_floor (default 0.15)
  • Env: IBEX_COMPOSITE_RELEVANCE_FLOOR / IBEX_MEMORY_COMPOSITE_RELEVANCE_FLOOR
  • Floor must be < 0.5 (settings reject ≥ FTS sentinel) so FTS supplemental hits are not mass-excluded; gate semantics would only drop FTS when floor > 0.5

3) Chosen floor value and evidence

Candidate floors swept: {0.10, 0.15, 0.20, 0.25, 0.30}.

Default gold-set queries use min_similarity=0.5. Under that retrieval cutoff every vector hit that reaches scoring already has relevance ≥ 0.5, so floors in {0.10…0.30} never filter those candidates. Neutrality of precision@5 / recall@10 / MRR versus floor=0.0 on the default query loop is therefore by construction, not proof that the gate is safe under looser retrieval cutoffs:

Floorprecision@5 (default queries)recall@10MRRNotes
0.0 (baseline)1.01.01.0Committed baseline.json
0.10 … 0.301.01.01.0No candidate filtered under gold-set min_similarity=0.5

To exercise the gate inside the ranking-quality harness, gold set v1 includes synthetic bait gold.noise.relevance_gate_bait (orthogonal hotspot, confidence=1.0, usefulness=1.0, retrieval_count=1000). bench_ranking_quality.py runs a separate relevance_gate_probe with min_similarity=0.0 / limit=50 and fails closed unless the bait is excluded while a same-hotspot relevant memory remains. Unit coverage additionally lives in test_relevance_gate.py / test_relevance_floor_sweep.py.

Chosen default: 0.15 — below the FTS sentinel, filters near-zero noise when retrieval is opened, and preserves gold-set default-query metrics.

4) Why not services/context

Context assembly consumes already-ranked memory search results. It does not own composite scoring. No scoring module or proto changes under services/context/.

5) AssemblyOptions weight fields (not wired yet)

packages/proto/.../context.proto defines AssemblyOptions weight fields (recency_weight, relevance_weight, …). Those fields are unconsumed today: no Python/Go path applies them to composite_score or rank_hydrated_hits. Track C Non-Goals for this milestone exclude proto wire-contract changes. When 3.5.C.6 connects AssemblyOptions to composite scoring, per-request weight overrides must apply after the relevance gate (callers must not bypass exclusion by reweighting). Until that wiring exists, this is a forward-looking contract — not current runtime behavior.

6) Explicit non-goals

  • Do not change CATEGORY_HALF_LIFE_DAYS or recency_decay
  • Do not change RankWeights defaults
  • Do not reuse ADR-0040
  • Do not invent a context-side MemoryScorer
  • Do not change the context assembly proto in this milestone

Consequences

Positive:

  • Irrelevant candidates never enter the weighted sum when below the floor
  • Retrieval and scoring cutoffs remain independently tunable
  • Hot-cache write path and pure scorer unit tests stay stable
  • Ranking-quality harness probes the gate under opened retrieval, not only under min_similarity=0.5

Negative / follow-ups:

  • With production min_similarity=0.70, the scoring floor is mostly a safety net until retrieval is lowered
  • Operators who raise the floor > 0.5 would exclude all FTS hits — settings reject floor ≥ 0.5 so this cannot be set via env without a code change
  • AssemblyOptions weight overrides remain deferred to 3.5.C.6

Was this page helpful?

Edit on GitHub

Last updated on

PreviousADR-0067: Context capability catalog generate-and-diff for token budgetsNextADR-0069: Context packer bounded DP knapsack

On this page

  • Context
  • Decision
  • 1) Gate in memory read ranking, not in pure score()
  • 2) Configurable floor
  • 3) Chosen floor value and evidence
  • 4) Why not services/context
  • 5) AssemblyOptions weight fields (not wired yet)
  • 6) Explicit non-goals
  • Consequences
0%