Choose the route: discovery, ranking, or verification

For zvec-grep, the first search decision is not whether semantic search is fashionable; it is whether the question needs discovery, coverage, or both. This distinction supports a practical judgment: use the indexed layer when the relevant wording or location is unknown, use managed ripgrep when an exact anchor and complete occurrences matter, and combine them when discovery must lead into verification.

The distinction is easy to miss because all three routes can be presented through the same zg interface. A positional indexed query, a lexical query, a vector query, and zg --rg may all return file-oriented results. But similar-looking output does not mean similar evidence. The route determines what the system is trying to optimize and what a missing result can mean.

Start with the coverage question

Suppose you are investigating how a program restores a user preference at startup. If you already know the function name, exact error message, or configuration key, the problem is mostly one of locating and checking occurrences. If you only know the behavior—“where are saved preferences loaded when the application starts?”—the problem is different. The answer may be distributed across a loader, a state object, and an initialization path whose names you have not seen.

Managed ripgrep is built for the first problem. It scans workspace files directly and can match a literal, a symbol, a source fragment, or a regular expression. Its normal contract is exhaustive output: if the command is not deliberately bounded, it is meant to report all matches within the selected paths and filters. It does not require a semantic index or an embedding model. That makes it the appropriate route for questions such as these:

  • Where does the exact symbol loadPreferences occur?
  • Which files contain this error message?
  • Does a particular configuration key appear in every deployment file?
  • Which source lines match a known regular-expression pattern?

The value of exact search is not that it understands the system’s purpose. It is that it makes a narrow claim with a strong coverage contract. You supplied the pattern, and the tool reports matching locations in the workspace scope. The result still depends on path filters, ignore behavior, and the meaning of the pattern, but the retrieval method itself is not quietly choosing only the most promising matches.

The indexed route makes a different promise. It searches stored entity text with ranked lexical retrieval, vector similarity, or both. Vector retrieval can connect a question about “restoring saved preferences” with text that uses “hydrate state” or “load persisted settings.” Ranked lexical retrieval can anchor the search when the query contains useful words or identifiers. The output is a ranked sample, not an exhaustive occurrence list. Its purpose is to put likely evidence near the top when you do not yet know the vocabulary or location that will answer the question.

That is especially useful for relationship questions. Consider “Which component decides whether a request is rejected before it reaches the database?” The relevant evidence may not contain the word “decides,” and the component may be named after an error type rather than the behavior described in the question. An indexed search can surface a validation boundary, a middleware path, and a related exception even when no single exact term identifies the answer in advance. The result gives you candidates from which to reconstruct the relationship.

The trade-off is the meaning of absence. If an exact ripgrep search finds no occurrence of a known string, that is useful negative evidence within its selected scope. If an indexed search does not return a passage, several explanations remain open: the passage may rank below the requested limit, the file may be outside the index scope, the extractor may have represented it as a different kind of fragment, or the wording may not be close enough for the selected model. A ranked search can be highly useful without being a completeness test.

This is why zvec-grep keeps the routes distinct rather than hiding exact search behind a semantic wrapper. Replacing exact matching with vector similarity would make a task that asks “where does this occur?” depend on relevance estimates. Replacing discovery with a literal search would make a task that asks “how does this work?” depend on a name the investigator may not know. One interface can reduce switching costs while still preserving different standards of evidence.

A route is also a statement about cost. An indexed query assumes that a workspace index exists and that its stored representation and embedding model are available. Building and maintaining that index can be worthwhile for repeated, ambiguous, cross-file questions. It is unnecessary overhead for a one-off search for a distinctive string. Managed ripgrep can answer that one-off question directly, without embedding the workspace first.

flowchart TD Q["Workspace question"] --> K{"Is an exact anchor known?"} K -->|"Yes, and completeness matters"| RG["Managed rg: exhaustive exact or regex search"] K -->|"No, wording or location is unclear"| SEM["Indexed discovery: ranked sample"] K -->|"Partly; discovery must lead to checking"| HYB["Hybrid indexed query"] SEM --> A["Inspect source-linked evidence"] HYB --> A A --> V{"Need every occurrence or exact proof?"} V -->|"Yes"| RG V -->|"No"| STOP["Use sufficient ranked context"]

Use hybrid retrieval when discovery and verification meet

The indexed path is not a single opaque mode. Zvec-grep lets a request express different kinds of intent through query groups. An ordinary hybrid query combines a natural-language query with lexical and vector retrieval. An explicit FTS route asks for ranked lexical matches. An explicit vector route asks for conceptual similarity without lexical ranking. Several groups can be kept separate so that their results remain attributable to their individual queries, or fused into one ranked plan when the goal is a combined candidate list.

The important point is that these routes are signals, not hard guarantees. Adding an exact-looking term to a semantic query does not turn the response into an exhaustive lookup. Adding a vector route does not mean every conceptually related passage will be returned. The query tells zvec-grep how to gather and rank candidates; it does not erase the boundary between a ranked sample and direct file coverage.

A useful mixed investigation has stages, even if they happen through one interface. First, express the behavior or relationship that is not yet anchored to a location. For example, ask where startup restores saved preferences and include a known product term only if that term is a genuine clue. Read the returned snippets and source ranges as discovery evidence. If they reveal a symbol such as restoreSession, a file path, or a distinctive error, use that concrete anchor in exact search. The first route reduces uncertainty about where to look; the second tests the claim you now know how to state.

This staged approach also protects against a subtle failure: allowing an inferred name to replace the original question. Suppose your first results suggest that a class called PreferenceStore is involved. Searching only for that class may find its definition while missing a separate startup adapter that actually triggers restoration. The inferred symbol is useful as a lexical anchor, but it should supplement the relationship-focused query, not redefine it. Zvec-grep’s agent guidance makes the same distinction: when the answer spans multiple files or components, semantic intent and exact anchors can travel together in a mixed search.

Separate query groups are useful when their roles should remain visible. A semantic question about “where authentication is validated” and a lexical constraint for ForbiddenError may produce overlapping evidence, but the overlap itself carries information. One hit may be recalled by meaning, another by the exact error name, and a third by both. Keeping group metadata lets a reader or agent see how the candidate entered the result set. Fusing groups is useful when a single global ordering is easier to consume, but it combines rankings for presentation; it does not prove that the routes agreed on the underlying explanation.

Consider how the same investigation changes as its uncertainty falls. At the beginning, “where are credentials checked before a request is accepted?” is a semantic question. After discovery returns a likely validation service and a named exception, an explicit lexical route can test whether those anchors are present in the indexed representation. Once the relevant files are known, managed ripgrep can check every occurrence of the exception or the call that raises it. Each step answers a narrower question than the one before it. This is not redundant searching: the first step discovers vocabulary, the second strengthens the association with known terms, and the third checks a coverage claim against the files themselves.

A vector route is also conditional on the index’s model and content support. It cannot inspect a file that the scanner excluded, and it cannot recover structural information that was flattened or omitted before embedding. A lexical route can be more dependable for a distinctive identifier, but it still searches the stored indexed text when used through the index. If the exact identifier is important and the claim is exhaustive, direct ripgrep remains the stronger final check. Route selection therefore follows the evidence still missing, not a preference for one algorithm.

Filters narrow the workspace under consideration; they do not turn a ranked list into a complete one. Restricting a query to a source directory or a file type can make the returned candidates easier to interpret, while also making any conclusion apply only to that restricted scope. A result that disappears after a path filter may be excluded by the filter rather than irrelevant. Conversely, a result that appears in an indexed search says little about files that were never admitted to the index. Good route selection keeps the scope in the claim: “among these indexed TypeScript files” is different from “nowhere in the workspace.”

The same reasoning applies to multiple natural-language questions. If you ask separately about preference loading and preference persistence, separate groups preserve the distinction between the two intents. A fused result can prioritize entities that respond to both, which may be helpful for finding a shared component. But the fused list should not be mistaken for two exhaustive answers. It is a compact way to allocate attention across related questions.

The practical decision can therefore be made with three tests.

First, is the anchor known? A literal, filename, path, symbol, or regular expression points toward managed ripgrep. A description of behavior, cause, sequence, or relationship points toward indexed discovery. If both are present, retain both rather than forcing one to do the other’s job.

Second, is completeness part of the claim? Words such as “every,” “all occurrences,” “does this appear anywhere,” or “list each match” raise the standard from relevance to coverage. Exact ripgrep is the appropriate default for that part of the work. Indexed results can help find the relevant area, but they do not substitute for the exhaustive check.

Third, is the answer distributed across files? A definition lookup may be settled by one exact match. An architectural or causal explanation usually is not. Start with indexed search when you need to discover a call relationship, a lifecycle, a dependency, or a design rationale, then read the source-linked results and verify critical anchors. The need for multiple files is a reason to begin with discovery, not a reason to abandon exact search at the end.

These tests expose several tempting but unreliable shortcuts. “Use vector search for everything” ignores the index prerequisite and turns straightforward coverage questions into relevance problems. “Use grep because it is exact” ignores the fact that exactness about a guessed term does not answer a question whose vocabulary is unknown. “Fuse every possible route” can produce a denser list, but more signals do not automatically mean better evidence; they can also increase the work of explaining why each result matters.

Zvec-grep’s result limits make this distinction operational. Indexed search returns a bounded set per query group, and the CLI preserves group-specific rankings rather than pretending that every group’s candidates form one exhaustive universe. The MCP search response is intentionally compact, with short source snippets and locations so an agent can judge relevance without receiving the full content of every hit. That compactness is helpful only if the recipient understands what it leaves out. A short passage is an efficient starting point, not permission to infer that unshown occurrences do not exist.

The best stopping rule is consequently conditional. Stop after indexed discovery when the question asks for a concise, source-grounded explanation and the returned evidence covers the relevant relationship clearly enough to read. Continue with exact search when the answer must name every occurrence, confirm a configuration pattern, or support a negative claim. Continue with more focused source reading when the snippets identify a promising component but not the causal link. Change scope or inspect index status when the results suggest that the relevant material was never admitted or is not current.

Choosing a route this way keeps zvec-grep in its intended role. It is not an oracle that turns uncertainty into certainty. It is a way to move from an unknown workspace question to a smaller set of evidence, while retaining a direct path for checking what must be complete. Once that route decision is clear, the next issue is upstream: the indexed path can only discover what the workspace scanner admitted and what the extraction pipeline preserved.

References