ADR-0148: Neural-semantic retrieval lane (Exa) as a third adverse-media/discovery source
Date: 2026-07-27 Status: Accepted Deciders: Adrian (Soft4U), Claude (Opus 4.8)
Decision context:
- Latency: not measured under load because the lane is dark-launched OFF (
exa_search_enabled=False) and runs at most once per adverse-media query, bounded by a 25 s client timeout, only on theadverse_mediasearch type. A single live call measured ~1–2 s. - Dependency surface: no new package — a direct
httpxREST call tohttps://api.exa.ai/search(httpx already used by the Tavily/SERP lanes). One new secret (exa_api_key) + one flag. - Debuggability: each hit carries
_provider='exa'provenance (ADR-0077); a lane failure logs adata gapwarning and returns([], True); no exception escapes the lane. - Reversibility: single config flip (
exa_search_enabled); flag-off is byte-identical (the lane returns([], False)and the merge helper is a pass-through). No migration. - Blast radius: additive. The lane merges hits into the existing per-query bucket and folds its degraded status into the existing fail-closed marker; it introduces NO new severity/scoring path.
- Alternative considered: an embedding index we build/host ourselves (rejected for now — Exa is a managed neural-search API; self-hosting an index is a large, separate effort with no near-term payoff).
Context
Keyword/boolean SERP (Tavily) and BrightData under-rank semantic associations that a neural/embedding search surfaces directly. On the OB Holding 1 OÜ live investigation (2026-07-27) the group's Lithuanian enforcement history — OlyBet ↔ Olympic Casino ↔ a €8.4m AML fine — was missed by the keyword lanes. A single neural search for "OlyBet Olympic Entertainment Group money laundering enforcement" returns, as its top result, "Lithuania fines Olympic Casino €8.4M for AML failures" — the exact signal the boolean lanes under-ranked. This is the one genuine competitive-recall edge worth neutralising, built entirely as our own lane (issue #767 is explicit: build our own lane, never reference or port any competitor codebase).
Forces:
- The adverse-media agent already runs a two-provider merge (Tavily SERP + BrightData
SERP) per query, with a fail-closed "degraded" contract (a provider that could not run →
a data gap, never a clean empty) and honest
_providerprovenance. - The load-bearing non-goal of epic #730: no soft-averaging or alternative scoring path. A new retrieval source must not become a new way to raise (or lower) risk — it feeds the SAME deterministic escalators through the SAME two-lane attribution (ADR-0078).
Decision
Add Exa as a THIRD retrieval lane (_exa_search) in the adverse-media agent, scoped to
the adverse_media search type (_is_neural_lane_query) — sanctions/PEP are exact-match
list screens where neural search adds noise, not recall. The lane:
- Is a direct REST call (no LLM, no agent). It maps Exa's embedding-ranked documents to
the SAME hit shape (
title/url/content/_provider='exa') as the SERP lanes, soattribute_hit/hit_names_in_scopetreat a neural hit identically — verified- identifier lane vs unverified-candidate lane, unchanged. - Is folded into the existing per-query merge via a
_finishhelper: its hits are deduped into the same bucket and its degraded status is OR-ed into the query's degraded flag. Embeddings + ranking stay inside Exa; no scoring is added here. - Is fail-closed-not-empty: enabled-but-unconfigured / timeout / transport / non-200 →
([], True)(an incomplete-coverage data gap, surfaced by the existingdegraded_search_types→ screening-gap finding), never a benign "no matches". Flag-off →([], False)(no contribution; byte-identical to today). - Is dark-launched behind
exa_search_enabled(default False); the key lives inexa_api_key.
Consequences
Positive
- Recovers the semantic-association recall the keyword lanes miss (validated: the €8.4m LT fine surfaces as the top neural hit) without any new scoring path.
- Additive and reversible; flag-off is byte-identical, and all fail-closed / never-suppress invariants (ADR-0067) and two-lane attribution (ADR-0078) are preserved by construction.
Negative
- A third metered external API — cost per adverse-media query when enabled; mitigated by
the flag, the
adverse_media-only scope, andexa_num_resultsbudget. - A neural search can return topically-adjacent but off-subject documents; these are handled by the SAME subject-relevance guard + two-lane attribution as every other lane (a name-only neural hit stays an unverified candidate, never a subject floor).
Neutral
- Adds
_provider='exa'as a fourth provenance value; report/UI provenance rendering is unchanged (it already reads_provider).
Alternatives Considered
Alternative 1: Route neural search through an LLM agent (like the earlier SERP agent path)
- Why rejected: the agent-wrapped SERP path timed out under concurrent load (ADR-0077); a direct REST call is deterministic and faster, and neural ranking needs no LLM in the loop.
Alternative 2: Self-hosted embedding index over our own corpus
- Why rejected (for now): large, separate effort; Exa is a managed neural-search API that reaches the open web (where regional enforcement press lives). Revisit if cost or coverage warrants.
Alternative 3: Do nothing (keep two lanes)
- Why rejected: the keyword lanes provably miss brand↔enforcement semantic associations (the OB LT gap), a never-suppress recall hole.