Repository navigation
Search reads each index once for names, folders, words and meaning - #596
Merged
Merged
Conversation
Each library's index is walked by one aggregate gufi_query whose per-directory step adds name matches, files below a matching folder, the Studio's word matches and its nearest passages by meaning, each into one per-thread table, ranked once at the end. The query's embedding goes in -I as a blob literal. A directory without a words or vector table fails only the steps that need it. External databases in the shared text and vector schema (ext_bm25_by_file, ext_vectors_by_file) are read in the same walk with --external-copy, matched to entries by inode and mtime. The kinds are fused with reciprocal rank fusion (k = 60), within each walk and again across libraries, ties to the more exact kind and then the path. Hits carry mode (now including path), kinds and the fused score; the Search page labels a folder match and lists every way a hit was found. Libraries on one remote host share one gufi_query with their roots stacked, one round trip per host, sent as gufi_query alone. Local libraries keep a walk each: stacked local roots measured no faster (STUDIO_STACK_LOCAL=1 stacks them). On a 24,000-directory synthetic index with words in a quarter of the directories and vectors in a tenth, a search with meaning went from about 2.9 s to about 1.0 s.
Matthew Shaxted (mattshax)
added a commit
that referenced
this pull request
Oct 25, 2026
…n gufi_vt too The one walk (#596) is the default search; the gufi_vt setting takes its own route, through the same check before anything runs.
Matthew Shaxted (mattshax)
added a commit
that referenced
this pull request
Oct 25, 2026
… "not indexed yet" (#597) * Query: text conditions run through gufi_vt; an unbuilt main folder is "not indexed yet" A Query across libraries with a "has the words" condition answered through the per-library merge, not gufi_vt. gufi_vt takes each SQL stage as a double-quoted value read verbatim, so the Studio refused any stage holding a double quote, and a text condition always has them: its FTS5 phrase query is MATCH '"a" AND "b"'. A double quote inside a single-quoted string is now written for gufi_vt as '||char(34)||', which gives the same string, so text conditions run through gufi_vt with the rows the merge gives. A double quote outside a string (a quoted identifier, a comment) still sends the query to the merge. A Query across libraries answered by the merge now carries mergeReason saying why. The main folder counted as indexed whenever its index folder existed, even empty before the first build. Search then found nothing there and named nothing, while Query ran gufi_query, failed, and said "index not found". It now counts as indexed once its index has a db.db at the root, as the index status already does, and both name it alike: "not indexed yet" before its first build, "index not found" for one built before whose index is gone. check_app.sh's gufi_vt check adds a query with a words condition. * Test: an unbuilt main folder is named "not indexed yet" with search on gufi_vt too The one walk (#596) is the default search; the gufi_vt setting takes its own route, through the same check before anything runs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Search reads each library's index once for every kind of match: names, files below a matching folder, the Studio's words, the Studio's vectors, and external databases in the shared text and vector schema. The kinds are fused with reciprocal rank fusion. This follows the one-pass pattern GUFI's authors recommend: one
gufi_querywhose per-directory step adds every kind while the directory's tables are open, merged per thread and ranked once at the end.What changed
walkSearch(server/src/gufi.ts): one aggregategufi_queryper library, or per remote host, with these stages:-I: a per-thread table, plus the query's embedding as a blob literal, one copy per thread. That works on every GUFI in use;--global-dbwould do the same on newer ones.-E: four statements at most, in a fixed order:words, bybm25);gvec, by distance), with the query vector picked by the directory'sgvecmodel.A directory without a
wordsor vector table fails the step that needs it, and only the later steps are skipped there. No empty tables are written. gufi_query compiles every statement again in every directory, so there are few.--no-print-sql-on-err: each such failure prints one short line, and only the last 16 KB of error output is kept. A GUFI from before 2026 that does not know it, or--external-copy, is asked again without them.-G: ranks each kind, keeps each file once per kind and to the kind's limit, and fuses the kinds with RRF, k = 60.fuseRows: the same fusion across libraries and walks. Ties go to the more exact kind (words, name, folder, meaning), then the path, so the order is deterministic. Hits carrymode(the most exact kind, now includingpath),kinds(every way the file was found) andscore.Folder-path matches: files below a folder whose path below the library root matches, with the same term rules as names. A file whose name matches counts as a name match only.
External databases in the shared schema, read with
--external-copy. Each file in an index directory other thandb.dbwhose name matchesSTUDIO_EXTERNAL_DB_PATTERN(default\.db$; empty turns it off) is attached, and two tables are read:ext_bm25_by_file: text bybm25.ext_vectors_by_file: for avector_model_namewhose query vector the Studio has (all-MiniLM-L6-v2, which the bundled ONNX model quantizes), by cosine distance put on vec0's scale.Rows join the directory's entries on
binodeandbmtime, so a file changed since extraction is not matched. A library's "words not searched" note is withdrawn when its external databases answered.Remote: libraries on one host and account share one
gufi_querywith their roots stacked, one round trip per host. Before, each library ran one remote command for names and words, and none for meaning. Rows go back to their library by the root they are under. A walk past its deadline names every library in it.Local: libraries keep a walk each. Stacked local roots measured no faster;
STUDIO_STACK_LOCAL=1turns stacking on.Fallback for meaning: a library whose index has vectors but whose walk returns none (embedded with the earlier GGUF model) is searched by meaning the earlier way. The gufi_vt search setting keeps its earlier path.
The assistant's
search_kbusessearchScope's fused order directly.The Search page labels a folder-path match "folder", and the label's tooltip names every way a hit was found.
Measurements
On a synthetic index of 24,000 directories and 100,000 files, words in a quarter of the directories and vectors in a tenth, warm cache, median of five, on a shared host (load average 1 to 10):
What each part adds to the walk: folder-path matching about 0.15 s, vectors about 0.1 s, the external database lookup about 0.05 s.
Remote, by round trips: one per host per search, with meaning included where the remote index has vectors. Before, it was one per library, with no meaning.
Notes
wordstable takes a 48 KB directory database to 68 KB, and empty vector tables take it to 108 KB. That is about 0.4 GB and 0.8 GB more on this test index.Tests
server/test/searchOnePass.test.mjs, with real GUFI where installed:server/test/searchStackedRemote.test.mjs: two libraries on one host give one remotegufi_querywith both roots, another host has its own, and a failing host names its library.libraryScope.test.mjs.