Repository navigation
v7.76.0
Pairs with engine-service-v0.1.156 (REQUIRED_ENGINE_VERSION moves from 0.1.154; v0.1.155 was deployed to the managed service and superseded the same morning, never pinned). Search latency on the MCP path. Telemetry is written off the request path, a first search no longer waits on the full stats listing or a single-flight route probe, the topic reads overlap the catalog attach, the MCP server warms a first search's cold steps at start, and the MCP tools ask the engine for at most 300 characters of each row's text. Measured against the managed service on v0.1.156, a warm default MCP search takes 2.53 s on this release and 3.67 s on 7.75.0. The engine's cardinality router now searches collections of up to 30,000 rows exactly, which fixes a full page missing its nearest rows.
Changed
- MCP search telemetry is written off the request path (nexus-vpa9q). The MCP
searchandquerytools wrote each search's telemetry batch synchronously on a fresh HTTP client: 0.42 to 0.47 s of a default search and 0.29 s of a query. They now hand it to a process-lifetime background writer (a bounded queue of 64 batches, dropped when full, flushed for up to 2 s at exit and on SIGTERM) that writes through the pooled T2 client.telemetry.search_enabledstill gates it before anything is queued.nx searchis unchanged. - A cloud client whose version probe saw the per-collection route skips the route's single-flight first probe (nexus-vpa9q). The cloud version probe already reads the engine release and refuses anything below
REQUIRED_ENGINE_VERSION, so a release at or above the route's first engine marks the route confirmed, and a first search's model groups run concurrently instead of one after another. A later write-off (an edge refusal, a 5xx) still resets it to the probe. Local mode is unchanged. - The topic reads overlap the catalog attach, and the MCP tools read taxonomy through the pooled T2 client (nexus-vpa9q). After retrieval a search attached catalog ids and then read topic assignments and link pairs, serially (0.4 to 0.6 s and 0.5 to 0.7 s on a first cloud search). The topic reads key on chunk ids only, so they now run on a worker started before the catalog attach. The MCP tools no longer build a T2 client set per call for the taxonomy reads, which opened a new TCP/TLS connection on every search. A failed read still leaves the results unboosted, as before.
- The search fan-out floor never waits on the full stats listing (nexus-vpa9q). A cold process fetched
GET /v1/vectors/stats(about 1 s) before its first search fanned out, only to feed the floor that drops a near-empty collection beside a populous sibling. The floor now uses counts of any age, refreshes them in the background, and excludes nothing when it has none, which is what it already did on an unknown count.store_listand other callers that print sizes still read fresh counts. A cold first default search went from 4.33 to 4.57 s to 3.12 to 3.70 s against the managed service. - The MCP server warms a first search's cold steps at start (nexus-vpa9q). On a daemon thread beside the install ping, it runs the cloud version probe, two routing listings (filling the names cache and two pooled connections), an empty link-pairs read and an absent-id catalog lookup. Each step is best effort, and it never asks for the full stats listing, so a session that never searches does not pay for it. It takes 0.8 to 1.1 s in the background, and the first default search after it took 2.77 to 3.06 s against 3.30 to 3.77 s cold.
NX_MCP_SEARCH_WARMUP=0turns it off. The cloud probe's/versionread also seeds the client's embedding-mode memo, which removes a secondGET /versionfrom every first search. - The MCP
searchandquerytools ask the engine for at most 300 characters of each row's text (nexus-tao37). New optional request fieldcontent_chars(integer >= 1) onPOST /v1/vectors/search-per-collection: the engine caps each returned row's text at that many code points, after the rerank, so the rerank still scores full text, and echoes the field in the envelope. Row text was 44 to 49% of every response, and the MCP page shows only 200- to 300-character snippets of the rows it displays. The cap makes the responses 33 to 40% smaller, with engine handler time unchanged.nx searchsends nothing and keeps the full text. An engine that predates the field ignores it and answers with full text. - The engine logs where each per-collection search arm's time goes (RDR-226 Phase 0; engine change). Each
search-per-collectionrequest logs oneevent=search_per_collection_arm_phasesline with per-request sums and maxima of permit wait, setup (pool connection plus the settings stamp), router probe and statement time, the remainder, and the exact and HNSW arm counts. Plain search is unchanged.
Fixed
-
An engine stopped during a schema migration no longer blocks the next boot for five minutes and then fails it (nexus-8sph2; engine change, takes effect with the next engine tag and, locally, the client release that pins it). Liquibase's changelog lock is a committed row in
databasechangeloglock. An engine killed mid-walk (SIGTERM, CTRL_BREAK on Windows, a crash) left itlocked=true, and the next boot loggedWaiting for changelog lockfor about 300 s and failed (measured 2026-10-05 on qwentescence). The migrator now takes a PostgreSQL session-level advisory lock on its migration connection before Liquibase runs. PostgreSQL releases it when the holding session ends, however the process stopped, so a migrator that holds it knows no other migrator is walking. A locked row it finds is stale, and it releases that row through Liquibase's own lock service, loggingevent=schema_changelog_lock_stale_releasedwith the row'slockedbyandlockgranted. A migrator that finds another live migrator's advisory lock waits up to five minutes (event=schema_migration_lock_waitnames the holder's pid), then refuses to boot, and never touches the live walker's row. Liquibase's own lock stays, so an engine without this change still waits on the row. The one case it does not cover: an older engine and a newer one booting against the same database at the same moment, where the newer one would release the older one's live row. -
The engine searches a collection of up to 30,000 rows exactly instead of walking the HNSW index, so a full page no longer misses its nearest rows (nexus-nqsa7; engine change, live in the cloud from engine-service-v0.1.156, and locally from the client release that pins it). The cardinality router's default
NX_SEARCH_EXACT_MAX_ROWSmoves from a provisional 10,000 to 30,000. Above the threshold an arm walks its model leaf's HNSW index underrelaxed_order, which stops at the first rows the collection filter admits. In a leaf shared by many collections the filter discarded about 80% of the walk, and a 27,893-row code collection returned a full page whose best row was 0.348 where the true nearest was 0.305 (recall against exact 0.925 at k=40, 0.983 at k=120). The exact re-run only fires on a short page, so it never saw this. engine-service-v0.1.155 shipped 60,000 and fixed that search live, but the 45k- and 58k-row collections then searched exact too: five exact arms ran at once at about 2.5 times their solo cost, those two at 1.1 to 2.0 s, and a warm default search got about 1 s slower (adding fan-out permits would add concurrent scans, not relieve them). At 30,000 the repro collection and a 29,355-row one stay exact and the two largest go back to HNSW, where the same miss can still happen. SetNX_SEARCH_EXACT_MAX_ROWShigher to trade warm latency for recall on larger collections. Measurements: T2conexus/nqsa7-fork-hnsw-vs-exact-2026-10-09.
Developer workflow
- The unit suite refuses to start, once, when the engine substrate cannot boot, instead of erroring every substrate-backed test at setup (25,636 setup errors on one full
-n autorun, all one fact). At session start, on the xdist controller and after the lease gates, it checks that the service jar exists and is newer thanservice/sources, and that the pinned engine tag's PG bundle is available (provisioning it once, before the workers spawn, when it is not cached). On either failure it exits 75 with one line: the jar case namesscripts/build-gate-jar.sh; the bundle case names the pinned tag and says it is not published or its bundle is not downloadable (a release branch whose engine is still building). It replaces the stale-jar banner, which only warned.NX_TEST_T2_SUBSTRATE=noneskips it, as does a nested pytest inside a lease holder. Shared-memory exhaustion (nexus-6qp25) is a different cause and still shows as a wall of setup errors. - Implementer dispatches get a brief checklist (nexus-yjg4v). The SubagentStart hook injects five questions into every implementer dispatch (developer, general-purpose, claude and debugger agent types; not explorers, reviewers, critics, validators, planners or researchers): the change's consumers, its final display point, local and cloud modes, resource bounds, and cache and shared state. The agent resolves any question the brief leaves open before its first edit. The
developmentskill carries the same list for the brief's author.