Skip to content

fix(search_packages): relax nix package search constraints and score on the tool side - #647

Merged
Scott McMaster (scottmcmaster) merged 3 commits into
mainfrom
08-07-scott-search-packages-scoring
Aug 28, 2026
Merged

fix(search_packages): relax nix package search constraints and score on the tool side#647
Scott McMaster (scottmcmaster) merged 3 commits into
mainfrom
08-07-scott-search-packages-scoring

Conversation

@scottmcmaster

@scottmcmaster Scott McMaster (scottmcmaster) commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

This is for issue 617 -- another try at making the search_packages tool return good results with less churn (my white whale).

The ^ and $ anchors overconstrain the nix search so you can see this kind of churn:

Screenshot 2026-08-07 at 1 36 35 PM

With this change:

  1. We no longer anchor but just let the query term tokens pass through to nix search. This gets us a lot more candidate results. So...
  2. Then we collect those across all channels (no longer just each channel individually).
  3. And do some heuristics to score them. We can improve these heuristics later, do Manhattan distances or other kinds of spelling/sounds-like correction, let an LLM do the scoring, or whatever -- but I think what's here is pretty effective and also fast/easy.

Then you can see much happier results more like this (note the scoring implied in the agent's evolution logs in the UI):

Screenshot 2026-08-07 at 3 39 44 PM

In addition to not missing pretty obvious things, by return just MORE results, we give the agent the opportunity to make better decisions in future search and edit steps.

Test Plan

Some new unit tests, plus manual testing.

  • No test plan needed

Docs

  • Docs updated (companion PR in darkmatter/nixmac-web: #___)
  • No docs update needed

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@scottmcmaster Scott McMaster (scottmcmaster) changed the title scott-search-packages-scoring fix(search_packages): relax nix package search constraints and score on the tool side Aug 7, 2026
@scottmcmaster
Scott McMaster (scottmcmaster) marked this pull request as ready for review August 7, 2026 07:57

@prelint prelint Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

The process_results sort closure calls relevance_score(a, &a.name) — scoring each result against its own name rather than the user's search query.

apps/native/src-tauri/src/evolve/search_packages.rs:265

Caution

When use_regex = true, the new code calls regex::escape(query) before passing it to nix search, which escapes all regex metacharacters and turns the query into a literal string search.

apps/native/src-tauri/src/evolve/search_packages.rs:84

2 finding(s) posted as inline comments.

Comment thread apps/native/src-tauri/src/evolve/search_packages.rs
Comment thread apps/native/src-tauri/src/evolve/search_packages.rs Outdated
@prelint

prelint Bot commented Aug 7, 2026

Copy link
Copy Markdown

Ship with changes Package search broadened from anchored constraints to client-side token scoring

Product decisions in this change

Agree with concerns 1. Package search no longer uses anchor constraints. The agent receives candidates ranked by a scoring function with fixed weights (1000 for exact name, 500 for prefix match, 5 per description term).

The anchor removal solves a documented user problem: the PR screenshot shows the agent failing to find Spotify and similar obvious packages because the anchored query matched nothing. The scoring function correctly surfaces exact matches at the top. The remaining concern is that the weights were chosen by inspection and have not been validated against the eval suite or any systematic corpus. A weight calibrated for the demo case could perform poorly for multi-word queries or package sets with deeply nested attributes. The risk is bounded because the agent can search again with a modified query, so a suboptimal ranking degrades experience rather than blocking the task.

Agree with concerns 2. Results from all registered channels are pooled into one ranked list before the agent sees them, with no channel-preference signal.

The old channel-order bias meant that later channels never contributed when the result quota filled early. Pooling removes that structural problem and lets the best name match win regardless of channel position. The concern is that users who register a pinned or corporate channel expect that channel's version of a package to win for unambiguous queries. The scoring function has no channel-preference input, so a nixpkgs result with a marginally higher name score beats the pinned channel's result every time. No signal tells the agent which channel the winning result came from, so the agent may silently install from the wrong channel.

Option What it gives users What it costs Effort to change later
Current (pool all, rank by name) Best name match wins regardless of channel Pinned-channel guarantee lost Low: add a channel-weight input to relevance_score
Restore channel-order priority Pinned channel wins for unambiguous queries Later channels never contribute when quota fills Medium: restructure collect loop
Explicit channel-preference weight Users set a preference; scoring balances name and channel New user-facing concept High: requires UI and config schema

Disagree 3. Each channel's nix search results have no explicit candidate cap: the full output of every channel search flows into the scorer.

A query such as "python" or "vim" can return hundreds of results from nixpkgs. Three channels at hundreds of results each produces a large candidate pool. The scoring pass is fast, but the wall-clock cost of waiting for every channel's nix search process to complete before the agent sees any result is unbounded. The prior version of this PR included a per-channel cap for this reason. The current diff removes that cap with no replacement. Until PR-10 ships a per-tool deadline, a common-term search in a multi-channel setup can stall the evolution loop with no escape hatch.

Option What it gives users What it costs Effort to change later
Current (no cap) Scorer sees all possible candidates Unbounded latency on common queries Low: add one cap constant
Per-channel cap (e.g. 100) Bounded latency; scorer still gets a rich pool Rare deep-channel match may be cut before scoring Low: one constant
nix search --limit flag nix itself truncates early Behavior may differ across nix versions Low to medium

Agree with concerns 4. Platform-unavailable candidates are filtered after the agent's result count limit is applied, not before.

The new pipeline ranks candidates, truncates to the requested limit, then removes unavailable packages. If the top N results include M unavailable packages, the agent receives N minus M results rather than N. The old design continued collecting until it filled the quota with available packages. For most queries the practical impact is small because unavailable packages are rare. On an aarch64-darwin host querying a channel that carries many x86-only packages, the agent could receive far fewer results than requested, with no feedback explaining why the result set is small.

Agree 5. Derivation classification runs only on the final ranked set rather than on every candidate.

Classification reads derivation attributes to decide install method (cask vs. system package). Running it on every candidate before ranking wastes work on packages that will never reach the agent. The new design ranks cheaply first and classifies only the survivors. The test that counts classifier invocations confirms the behavior and guards against regressions.

Agree 6. Special characters in non-regex package names are now escaped for the underlying search tool, allowing literal searches like "c++".

The previous approach passed user terms directly as regex arguments, so a query like "c++" would be interpreted as a regex quantifier and produce no useful results. The new escaping converts each token to a literal POSIX ERE token before passing it to nix search. This is a correctness fix with no user-visible downside, and the new test covers the edge case.

Since the last review

  • Still open: Scoring weight values were chosen by inspection and have not been validated against real eval cases showing search churn. (No eval run is referenced in the PR description or the single Graphite stack comment. Weight constants are unchanged from the prior diff.)
  • Still open: Channel pooling removes the guarantee that a pinned or corporate channel's result surfaces first for unambiguous queries. (No channel-preference signal was added to relevance_score. The PR description and conversation contain no acknowledgment of this tradeoff.)
  • Still open: Per-channel candidate cap bounds latency; removing the early-exit means all channels run to their cap before any results reach the agent. (No per-channel cap constant appears in the current diff. search_single_channel passes all nix search JSON output to process_search_output without a limit, and no --limit flag is present in the nix command construction.)
  • Still open: Score weights were not validated against the eval cases that showed search churn, and no eval suite run is mentioned. (No eval results or eval run command appears in the PR description or comments.)
  • Still open: No user segment using a corporate or pinned channel was identified, and the pooling change removes the channel-order guarantee with no acknowledgment. (No statement about pinned-channel users appears in the PR description or the single reviewer comment.)
  • Still open: The per-tool deadline from PR-10 is not yet shipped, leaving multi-channel searches on common terms with no wall-clock bound. (The combined plan still lists PR-10 as a Phase 1 item. No deadline mechanism appears in this diff.)

Open questions

  • What does a common-term nix search (for example, "python" or "vim") return on a typical user nixpkgs channel, in terms of result count and wall-clock time? This determines whether the missing per-channel cap is an immediate latency problem or a theoretical one.

  • Has the scoring function been run against the eval cases that showed search churn, such as the Spotify case in the PR screenshots? Any result where the correct package ranks outside the top five would indicate a weight calibration problem.

  • Will PR-10's per-tool wall-clock deadline land immediately after this PR? Without it, three-channel common-term searches have no time bound.

Recommendation

Ship with changes
The anchor removal solves a real, screenshot-documented user problem and the scoring approach is the right product direction. The blocking issue is the missing per-channel candidate cap: without it, and without PR-10's per-tool deadline, nix search output on common terms is unbounded and the evolution loop has no escape hatch. Restoring a cap (the prior version of this PR had one) is a small change that directly addresses the one disagree verdict before shipping.

@scottmcmaster
Scott McMaster (scottmcmaster) force-pushed the 08-07-scott-search-packages-scoring branch from f4b793c to 4820a04 Compare August 10, 2026 06:12
@prelint prelint Bot removed the ship it label Aug 10, 2026
@darkmatter

darkmatter Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

🎨 Storybook preview

Open Storybook preview

Updated for f323f3a


⚠️ Detected UI changes (5)

These stories' HTML snapshots changed. I've added screenshots + links to the changed stories below. Review them carefully then accept the changes to regenerate baselines and include them in this PR:

Flows/Evolve › Playground

Flows/Evolve › Playground

Flows/Evolve › 1. Begin (idle)

Flows/Evolve › 1. Begin (idle)

Flows/Evolve › 2. Evolving (progress)

Flows/Evolve › 2. Evolving (progress)

Flows/Evolve › Evolving With Error Event

Flows/Evolve › Evolving With Error Event

Flows/Evolve › 3. Review (changes generated)

Flows/Evolve › 3. Review (changes generated)


Accept UI changes

  • Click here to accept these changes

Alternatively, you can run bun run test:update-snapshots locally to re-generate the baselines and then push the changes to this PR.

What does this do?

The screenshots above show UI changes detected by the Storybook
snapshot tests run on this PR. Each image is the rendered output of
a Storybook story from the code in this PR branch; the snapshot
test compared it against the committed baseline in
__snapshots__/ and flagged the difference.

Checking the box tells the darkmatter[bot] to regenerate the
baselines from this PR's current code and commit them directly to
this branch. The new baselines become the source of truth for
future runs — only accept after confirming the visual changes are
intentional.

Comparison baseline: the committed __snapshots__/ files on this
PR branch (carried forward from develop). Accept updates them in
place on this branch.

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor
Warnings
⚠️

No Linear issue ID found in this PR's title, description, or branch name (expected something like ENG-123). Add one so this work is traceable in Linear, or add #no-linear to the PR description to acknowledge it's intentionally untracked.

Messages
📖 No docs update needed — acknowledged.

📋 PR Overview

Lines changed 597 (+489 / -108)
Files 0 added, 2 modified, 0 deleted
Draft / WIP no
Has Test Plan yes
Linear issue no
No Test Plan Needed no
New UI components no
New Storybook stories no
New Rust modules no
New TS source files no
New tests no
package.json touched no
Cargo.toml touched no
Infra / CI touched no

🔬 Coverage

Report Lines Statements Functions Branches
apps/native/coverage/coverage-summary.json 35.6% 35.2% 30.5% 29.5%

Generated by 🚫 dangerJS against f323f3a

@linear-code

linear-code Bot commented Aug 17, 2026

Copy link
Copy Markdown

ENG-617

@scottmcmaster
Scott McMaster (scottmcmaster) force-pushed the 08-07-scott-search-packages-scoring branch from ea35db8 to b4b7d9e Compare August 26, 2026 06:29
Comment thread apps/native/src-tauri/src/evolve/search_packages.rs Outdated
Comment thread apps/native/src-tauri/src/evolve/search_packages.rs Outdated
Comment thread apps/native/src-tauri/src/evolve/search_packages.rs Outdated
Merged via the queue into main with commit 60bb5c0 Aug 28, 2026
12 checks passed
@scottmcmaster
Scott McMaster (scottmcmaster) deleted the 08-07-scott-search-packages-scoring branch August 28, 2026 01:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants