Repository navigation
v7.72.0
Added
-
Native Windows x64 can install and run the local engine and PostgreSQL bundle, but Windows is not yet declared supported (nexus-f9bgu, RDR-224; nexus-f9bgu.8, .11, .15 to .23, .25, .43, .47, .49 to .52, nexus-efk2h, nexus-ijue9.20). On Windows 10 and 11 x64,
nx init --serviceandnx daemon service install-binaryfetch thewindows-x64assets of the pinned engine tag (see Changed):nexus-service-windows-x64.txz, holdingnexus-service.exeand the four VC++ runtime DLLs it loads app-local, and a PostgreSQL 17.5 plus pgvector 0.8.2 bundle. Both are verified by sha256 and sigstore as on the other platforms. Windows on ARM and 32-bit Windows refuse with the reason. The executables carry no Authenticode signature; Windows code signing is deferred (nexus-dj01b). Windows stays unsupported until the RDR-224 Phase 5 gate passes on a clean machine in a native Windows Claude Code session (install, an MCP store-then-search round trip, a plugin hook firing,nx daemon service stop --with-pgleaving no process,claude -preturning); that gate has not run (nexus-f9bgu.26), and the desktop install path has not been walked (nexus-ijue9.21).What the client does on Windows.
nx daemon service install --autostartregisters a per-user Task Scheduler logon task,NexusStorageService, whose launcher runs underpythonwand starts the supervisor again 30 s after any non-zero exit, because the task's own restart-on-failure was measured to cover only a failed launch. Windows has no SIGTERM for this, so the supervisor runs in its own process group with a hidden console:nx daemon service stopsendsCTRL_BREAK, confirms by process exit and falls back to a hard kill, and the engine runs in a Job Object that is terminated after the grace. A service running in another Windows session is refused by name and never killed. At sign-out, restart and shutdown the supervisor stops the engine and PostgreSQL cleanly (a hidden window answeringWM_ENDSESSION, and an in-process PostgreSQL fast stop through its signal pipe, since a session that is ending cannot startpg_ctl). Before that fix every sign-out and restart on the clean Windows 11 guest crashed PostgreSQL and ran crash recovery at the next start; after it, sign-out took 0.46 s and restart 0.20 s with no crash recovery on the next boot. Sleep was not measured (a Hyper-V Gen2 guest cannot sleep). An engine or PostgreSQL bundle upgrade extracts the new files with the service still up, stops the service only around placement, places the file set as one unit with bounded retry and rollback, and restarts what it stopped.Identity and access. The service identity is the user's SID where POSIX uses the uid. Credential files that POSIX writes
0o600get an owner-only DACL, and the read check reads the ACL in place of mode bits. Every directory nexus creates grants the user's own SID, so a directory an elevated process made stays usable by a normal session (without it, a non-elevatednxgot WinError 5, measured), and a new Windows-onlynx doctorrow,Config directory access (non-elevated), names a config directory an elevated process made and prints theicaclsremedy.schtasks,nx,claudeandnx-mcpare never resolved through the current directory. Engine-side for Windows only:CTRL_BREAKstops the engine throughOrtInitGate, including during ONNX Runtime initialisation, and boot sweeps deadonnxruntime-javatemp directories, skipping any whose DLLs a live process holds.What was measured on Windows: the packaged engine and PostgreSQL archives from a release dispatch run passed the relocation smoke and the engine smoke on a clean Windows 11 Hyper-V guest with no developer tools and no VC++ redistributable (nexus-f9bgu.45);
nx initpassed on the same guest restored to its clean baseline (TLS entry under Changed); thewin-releaserehearsal runs the Windows test set natively (1249 passed on the TLS fix's run).nx self installon Windows is the separate entry below. -
The desktop extension lists Windows as a platform (nexus-f9bgu.43, nexus-ijue9.20).
mcpb/manifest.jsoncompatibility.platformsaddswin32. Windows has noexec, so the bundle's bootstrap runs the server as a child with the host's stdio handles inherited, resolvesuvthroughPATHEXTwithout searching the current directory, and puts itself in a kill-on-close Job Object first, so killing the bootstrap kills the server. Measured on a Windows host (Python 3.13): a stdio round trip and exit code 7 propagate, and the server dies with the bootstrap; without the job it survives. A Desktop session on Windows has not been run end to end. -
nx self installbuilds a new generation on native Windows without touching the venv a running process executes from (nexus-f9bgu.47, RDR-224). A new stdlib-only_install/generation_core.pystates the bash installer's orchestration in Python: it buildsgen-<stamp>with uv, pointscurrentandpreviousat it with directory junctions (create.new, rename aside, rename into place,rmdirthe old one, neverrmtreea junction; a crash between the renames is recovered by restoring the old pointer), and bridges a legacyuv tool installtree by reading its extras fromuv-receipt.tomland registering it as a junction in the GC ledger. The Windows shim is a PATH entry, not a file: each generation gets<gen>\binholding copies of its ownScripts\<name>.exelaunchers, and the user PATH (HKCU\Environment, type and every other entry preserved,WM_SETTINGCHANGEbroadcast) gets<tools>\current\binahead of uv's bin dir, so an upgrade only flips the junction. Nothing is written into~\.local\bin, because a running launcher exe cannot be renamed, replaced or deleted on Windows. Terminals and Claude Code sessions started before the one-time PATH change keep the oldnxuntil restarted;nx self gcremoves uv's old launchers once the legacy tree is gone, skipping any that are running. The reap comparesnormcase(realpath)forms with the\\?\prefix stripped for the current, previous and running-installer rules, deletes the receipt first, sayskept ... in userather thanreapedwhenever any file could not be removed, and keeps a half-deleted legacy tree's ledger pointer until the tree is gone.nx self installandnx self gccall it directly on Windows (neverbash, which is the WSL launcher); on Windowsrepair_uv_takeoverre-ensures the PATH entry and finishes a migration that stopped after the flip, andnx doctorchecks the PATH entry instead of shim contents. POSIX behaviour and every.shfile are unchanged. -
The engine serves per-collection top-K search in one request (nexus-tu8wp.1).
POST /v1/vectors/search-per-collectiontakes one embedding-model group of up to 256 collections and returns each collection's own topper_collection_k, so a dense collection can no longer crowd a small one out of a flatLIMIT. The engine embeds the query once, runs one single-collectionplain_search_<dim>statement per collection, applies the client's per-collection thresholds as each arm finishes into a heap that keeps the bestlimitby(distance, id, collection), and reranks once. Bounds:NX_SEARCH_FANOUT_CONCURRENCY(arms per request, default half the pool, clamped to the pool),NX_SEARCH_FANOUT_ARM_PERMITS(arms in flight across all requests, default half the pool, so plain search and writes keep the other half) andNX_SEARCH_FANOUT_BUDGET_MS(wall budget of one fan-out, default 20 s, under the edge's 30 s). A statement timeout, a dimension problem or the fan-out budget is isolated inper_collection[].errorwith a stableerror_kindand the other collections are served; pool or admission exhaustion, an expired request budget and any other transient database failure fail the whole request with 503. No changeset. Engine half; the client calls it from this release (nexus-tu8wp.2 below). -
A search asks the engine for each collection's own top-K, one request per embedding-model group (nexus-tu8wp.2).
search_cross_corpussendsPOST /v1/vectors/search-per-collectioninstead of batching a flat/v1/vectors/searchfan-out, so a dense collection no longer crowds a small one out of the pool and a default four-corpus search is 2 or 3 requests rather than 6 to 30.per_collection_kis the count the pre-batching fan-out used (max(5, n_results * mult), the group's largest multiplier),limitismax(300, 4n)clamped to 1200, or 1000 with rerank, and only finite thresholds are sent. A collection that times out or runs out of fan-out budget is reported infailed_collectionsand the rest are served; the dimension kinds join the stale-orphan handling. When the route cannot serve a group the batched path runs exactly as before, remembered per process: for 10 minutes after a 404, a 403 or any edge refusal, a 405 or a 501, and for 60 seconds after a 500, each logged at WARNING with its status; a 429 or a 502 to 504 (the gateway has already retried those) and a 400 or 422 fail the group instead. The first request is single-flight, so one search probes an engine without the route once. The MCP server keeps the memo for its lifetime, while eachnxinvocation is a fresh process and pays one probe against an engine without the route.NX_SEARCH_PER_COLLECTION=0(orfalse,off,no) turns the route off. A group whose collections the engine says mix embedding models is retried one request per collection through a pool of 8 and remembered for 10 minutes. A malformed envelope (a row without an id or distance, a non-numeric count) is refused as a whole and the group falls back. The reranked pool differs from the batched path's: with rerank the route thresholds each collection's rows first, merges the best 1000 per model group and reranks that once (the batched path reranked each batch's flat top 300, thresholds applied after), so the rerank-on page is not guaranteed to equal the old one; the final-page comparison is measured at nexus-tu8wp.3 after the route is deployed.--lexicalkeeps its/v1/vectors/hybrid-searchleg. Paired with the engine half in nexus-tu8wp.1; this release's pin to engine-service-v0.1.149 (nexus-f9bgu.43) carries it. -
The engine runs exact search for small collection sets (nexus-tu8wp.6). A plain-search statement, the flat one and each per-collection arm, first counts the rows its selected collections hold in the tenant, bounded at
NX_SEARCH_EXACT_MAX_ROWSplus one; at or below it the statement runs exact instead of walking the shared HNSW index, so a small collection no longer pays a capped HNSW walk (measured 5-43 s on cold arms, 2026-10-05) before the empty-result re-run. Above the threshold it is today's path;0disables the router. The default, 10000, is provisional until the conexus fork measurement sets it. The route is visible only in the boot line (event=search_exact_router) and inevent=vector_search_statement_slow, logged for a statement over 1 s with its route, probed rows and collections; a fast statement leaves no trace of it. No wire or status change.
Changed
-
This release pairs with engine-service-v0.1.149 (nexus-f9bgu.43).
REQUIRED_ENGINE_VERSIONmoves from 0.1.146 to 0.1.149, andPINNED_SERVICE_TAGwith it, so local-mode installs fetch v0.1.149. It is the first engine tag that publisheswindows-x64engine and PostgreSQL bundle assets. Since v0.1.146 the engine also carries the per-collection search route (nexus-tu8wp.1, first in v0.1.147), the cardinality router for plain search (nexus-tu8wp.6), the socket read bound on bounded statements (nexus-u9zkn), and the WindowsOrtInitGatebreak handling and ONNX Runtime temp sweep (nexus-f9bgu.8, nexus-f9bgu.11). No changesets: the one changelog file touched in the range changes an XML comment only. -
The conexus plugin's Python hooks launch through
uvinstead ofpython3, on every platform (nexus-efk2h). Sevenhooks.jsonentries (version-lockstep, subagent-git-write-gate, credential-print-guard, mailbox-drain and the threenx-hookshim entries) now run asuv run --no-project --no-config --quiet <script>, the launcher the sn plugin already uses, becausepython3is not on PATH on stock Windows and none of them fired there. Each script declaresrequires-python >=3.12in a PEP 723 block, so uv never picks an older interpreter. Measured cost: about 14 ms per hook on macOS and about 100 ms on Windows. All seven entries ran on stock Windows from the command line; that they fire inside a native Windows Claude Code session is a Phase 5 gate assertion and has not been observed. -
On Windows,
nxverifies TLS through the operating system's trust store (nexus-f9bgu.52). A fresh Windows 11 holds about 16 root certificates and fetches others on demand only through its own verifier, sonx initfailed downloading the PostgreSQL bundle withCERTIFICATE_VERIFY_FAILED; every urllib and httpx download (PG bundle, engine binary, ONNX models) was affected.truststore, a new Windows-only dependency, routes verification through CryptoAPI, activated once when thenexuspackage is imported. Verified on a clean Windows 11 guest with no priming request:nx initpassed in 53 s. macOS and Linux are unchanged. -
A search with no rerank plans 6
/v1/vectors/searchcalls instead of 28-30 at the MCP default fetch, and the candidate pool it reads is smaller (nexus-abdp2; Sam's 2026-09-29 stopgap "A" while the engine-side per-collection top-K, nexus-tu8wp, is built)._desired_candidate_countnow has two per-collection floors. The lean floor,max(5, n_results // 2), applies when nothing reads the depth of the pool; the deep floor, the oldmax(5, n_results * mult), applies when server rerank is on, the lexical leg is on, or the caller filters the pool after retrieval (nx search --pathand--max-file-chunkspass the newdeep_candidates=True). A group's pool never falls belown_results * multeither way, so a lone collection keeps its full over-fetch.What it buys: the MCP
searchtool fetchesn_results=30(limit 10 plus two lookahead pages) with no rerank. For a group of 67 voyage-context-3 and 25 voyage-code-3 collections that is 28 calls under the old floor and 6 under the lean one; on the cloud tenant now (98 collections in the default corpora, 30 calls old) a warm search went from a median 12.5 s to 5.7 s over four interleaved rounds, same 69 rows. Atn_results=300a 28-collection group needs 14 calls instead of 28.nx searchover several collections reranks, so it takes the deep floor and plans the same calls as before (12 calls, median 7.8 s against 7.5 s).What it costs: a smaller pool, roughly 125-170 rows where the old floor returned 300 at
n_results=10. The pool feeds server rerank,apply_ranking_boosts(hybrid, frecency, quality, topic), the--path,--max-file-chunksand file-diversity filters, the lexical leg's per-batch limit, the MCP footer's "of N" and the diagnosticsraw_count/dropped. The page a user reads after boosts, measured against an old-vs-old noise control (test_final_order_parity_old_vs_new_floor, T2nexus/measurements-abdp2-final-order-2026-10-05), stays within noise without rerank at fetch sizes 10 and 30, with and without the distance threshold (mean top-10 overlap 0.94-0.98 against 0.96-1.00 for old against old). With rerank the lean floor does not: mean overlap fell to 0.68-0.77 where old against old was 0.98-1.00 (one query at 0.11), and floors ofn_resultsandn_results * mult // 2recovered only 0.85 and 0.93, which is why the reranked path keeps the old floor. A--paththat selects a minority collection lost rows under the lean floor (18 survivors became 8 in one pair), which is why a post-filter takes the deep one. The reranked and filtered paths request the same candidates as before.The recall-parity gate (
tests/test_search_fanout_recall_parity.py, raw top-10 atn_results=10, no rerank) passes with every query at Jaccard 1.000 under the lean floor. That gate is blind to everything above, which is why the final-order variants were added. This change is also what moves four corpora (knowledge, code, docs, rdr) from the gate's split floor (0.6) to its strict 0.9 per query: atn_results=10only the 107-collectionallcorpus still splits. The strict floor fails on the reference's own jitter: OLD against OLD scored 0.818 on "release process and versioning" (docs) and the rdr query nexus-atylb recorded at 0.667, and the gate run at the old floor scored 0.818 on "error handling and retry logic" (code) and on the same rdr query; two intermediate floors each failed the gate once for that reason. The gate is therefore flaky for unsplit corpora independent of the floor (nexus-e9sux).
Fixed
- A search statement's read on a silent Postgres server now fails at a bound instead of waiting on TCP (nexus-u9zkn). During the 2026-10-05 production failover new connections recovered after 31.5 s, but a search read already in flight hung 59 s: the old server sent no RST and the engine set no network timeout, so the read waited on TCP. The bound is per path, not pool-wide, and it covers the statements a path bounds, not every read of the request. Every Java-set
statement_timeoutnow also gives that connection a socket read timeout of the statement bound plusNX_PG_SOCKET_TIMEOUT_MARGIN_SECONDS(default 30,0disables, a bad value fails the service at boot), set in the samePgSession.setLocalcall so the two cannot drift and restored when the connection returns to the pool; the statement bound is set before the borrow's othersetLocalround trips, so those are covered too. At the defaults a silent search statement ends at 30 + 30 = 60 s (taxonomy assign 60 s, reaper statements and gc batches 55 s, a bounded reaper census 90 s, scheduled sweeps 60 s, the sweep gate 35 s, tuple subspace list 40 s), which is about the 59 s the failover took: the gain is a deterministic end, not a faster one, and a smaller margin ends it sooner. Reads that stay unbounded: the token store's reads on an auth-cache miss and session resolution (TokenStore, plain connection, no stamp, no statement bound),CollectionRegistry.lookupon a cache miss, and every other read with no statement bound. A request can still wait on TCP in any of them, before or after its bounded statement. Paths with no statement bound get no read bound, on purpose: collection re-home (measured 128 s), the legacy orphan quarantine (5 m 41 s), purge-trash, delete and rename collection, the taxonomy link joins,VACUUM, and writes in general run for minutes with the server silent, and a socket timeout closes the socket without cancelling the backend, so a bounded write would roll back and its retry would stack a second backend behind the first. The tenant stamp that opens every borrow runs before a path sets its bound, so it gets its own read bound of the margin, never under 5 s, reset to none before the path runs. Some plpgsql functions (thegc_*andreaper_*ones) setstatement_timeoutthemselves, 5 s or 25 s, which no Java bound covers; the paths that call a 25 s function set a Java bound of 25 s, so their read bound (25 s plus the margin) always outlasts it.tcpKeepAliveis also set on the main and migration pools, by a class that does not loadPgSession, so a bad bound value still fails at the boot catch; its OS default of about 2 h makes it a backstop for idle connections only. Engine change; takes effect with the next engine tag. - The SessionStart upgrade hook no longer waits on
nx upgrade --auto, so a version change cannot time it out (nexus-wozn6). Since 7.68nx upgrade --autoruns the RDR-192 census whenever the installed version has no completion on file. Measured on 2026-10-05, that census took 51.5 s over 98 collections, plus about 2.7 s of other work, against the hook's 30 s timeout.nx-hook upgrade-autowaited on the child. After the 7.70 and 7.71 installs the hook was cancelled at 30 s (6 cancelled session starts in each window) and the ladder never recorded the rung for either version. The most likely cause is that the child died with the cancelled hook. That was not observed: nobody killed the old verb and looked at its child, and a census that defers records nothing either. The census cost also does not explain every cancel on record, since 23 fell on 2026-09-30, before 7.68 shipped the rung. The verb now startsnx upgrade --autoin a session of its own, discards its stdout, appends its stderr to<config>/logs/upgrade-auto-child.log(1 MiB, one rotation), writes oneupgrade_auto_child_spawnedevent with the pid and argv tohook.log, waits at most 2 s to catch annxtoo old to know--auto, and returns. No detached real run has been watched to completion. The check is the outcome: after the next version change, the upgrade ledger (nx upgrade --dry-run) advances to the installed version with no manual run; if it does not, the cause above is wrong. The child has no overall time limit, and several session starts after one install each start one (the rung's lock turns all but one into quick deferrals). On Windows the child starts withCREATE_NO_WINDOWand breakaway-from-job, falling back without breakaway; that path is pinned by a mockedPopenonly and has not run on a Windows host. - The SessionStart RDR summary returns inside its 10 s timeout (nexus-wozn6). It was repeatedly cancelled with nothing printed. On the cloud tenant it spent 3.3 s on a T3 existence check that lists every collection's stats, and 1.7-8.9 s fetching the whole
<repo>_rdrT2 project (1527 rows, 4.6 MB). The check now reads one collection's count (0.31 s), and the catalog lookup, the T3 check, the T2 fetch, the listing fallback and the git probes all run inside one 7 s budget, with the T2 fetch started first so it has the whole budget. End-to-end runs on the same tenant in the same minute took 9.63-9.84 s before the fix and 6.60-6.70 s with the first version of it (T2 fetched after the catalog and T3 legs), with no upgrade child running. Three runs of the shipped code (T2 started first) took 5.61 s, 5.71 s and 7.08 s; the 7.08 s run hit the budget with T2 late at 6.93 s. T2 is the dominant cost and is unchanged, so a fetch slower than the budget still happens: that session drops the status breakdown and rdr-fix pointers and says so on its own line, andhook.loggets anrdr_hook_t2_lateevent with the elapsed time so the rate can be counted. A narrower T2 read is nexus-pxp44. Two other cuts are said in the output rather than left silent: the rdr-fix check names itself as cut short when the budget ends it, and when the T3 check does not answer and the listing has no budget left, the line says whether the tree is indexed is unknown instead of "NOT indexed". One behavior change: the count is the collection's stored chunk count, so a collection whose chunks are all trashed or unowned now reads as indexed, where the tenant-wide listing counted live chunks and read it as absent.