← plans · search-matters-speed · NOS-21023 juil. 2026

search_matters 3.3s → tens of ms — design spec

Status: In progress · Author: Claude (NOS-210 orchestrator) · Date: 2026-07-23 · Repo: north-os (packages/agent-runtime/src/tools/search-matters.ts)
Related: Linear NOS-210 (retrieval-speed batch, Fix 2); implementation plan 2026-07-23-search-matters-speed.html

Problem (measured)

search_matters p50 is 3.3s live on the real HSE org (4.6k matters). The tool-level battery run for this spec measures 2.4–4.4s per call — including 2.4s for a query matching zero matters. Lawyers feel this as a 3-second stall every time the agent resolves "the 219 East 60th deal".

EXPLAIN (ANALYZE, BUFFERS) as app_role with the RLS GUC set shows two compounding causes:

  1. JIT compilation — not row work — is ~95% of the latency. The correlated mail-count and folder-count subqueries appear in SELECT and three more times in ORDER BY, and each per-token match expression appears in WHERE and again inside the ORDER BY match-count aggregate. The planner prices the duplicated correlated subplans at ~840 cost × ~2.3k estimated rows ≈ 1.9M–3.5M total plan cost, far past jit_optimize_above_cost (500k), so Postgres LLVM-compiles 188 functions with full optimization: 2,069ms of a 2,122ms single-token query; ~3.5s of 3.95s at three tokens. Real execution underneath: 55ms.
  2. Residual per-row subplan work. The folder-count subplan probes matter_drive_folder_org_idx (org-only index) then filters 3,833 rows by clio_matter_id per evaluation — ~0.85ms × 2 ORDER BY evaluations × N result rows (~340ms at 201 rows). Same pattern for the mail count.

Design

Single-file SQL restructure of the tool query. No schema change, no behavior change.

1 · Pre-aggregate both counts once (CTEs)

2 · Wrap ranking in a subquery

The inner query computes match_count, mail_count, folder_count once per matter; the outer ORDER BY references plain columns. No expression is evaluated twice.

3 · SET LOCAL jit = off in the tool's RLS transaction

After the rewrite the plan cost is ~19k (1 token) / ~68k (3 tokens) — below jit_above_cost — but a 5-token query still reaches ~117k and pays a 264ms JIT tax on 98ms of work. This query shape (LIMIT 25, sub-100ms row work, hashed subplans) never benefits from JIT; disabling it transaction-locally is strictly a latency floor.

Everything else — tokenization, entity/alias EXISTS matching, ranking tiers, non-identifying display-number handling, LIMIT 25, output formatting — is unchanged.

Measured result (prototype, real HSE corpus, app_role + RLS GUC)

QueryBeforeAfter (prototype)
Lexington (1 token)2,122ms (JIT 2,069ms)32ms
219 East 60th (3 tokens)3,994ms (JIT ~3.5s)53ms
5-token query98ms (with jit off)

Result identity: a 16-query SQL-level battery (single/multi-token, exact display number, client, entity, category label, apostrophe, LIKE metacharacters, no-match) returns byte-identical rows old vs new.

Alternatives rejected

Gates