Add a general search-entries host tool over the entry endpoint - #6068
Add a general search-entries host tool over the entry endpoint#6068FadhlanR wants to merge 2 commits into
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Preview deploymentsHost Test Results 1 files 1 suites 2h 6m 8s ⏱️ Results for commit 77b0ebd. Realm Server Test Results 1 files ± 0 211 suites +3 1h 20m 34s ⏱️ + 2m 38s Results for commit 77b0ebd. ± Comparison against earlier commit c636043. |
Four review findings addressed: - A realm that fails during the federated fan-out marks the merged document incomplete rather than throwing; the result card now carries that as an `incomplete` field and the tool description tells the model the total undercounts. - The client-side relevance re-sort now keys on the tool having added the sort itself (`composeSearchEntriesQuery` reports the addition), so a caller's explicit ordering — which may legally include `_matchRelevance` — is never overridden. - The fixed projection gains `item.name`, the file-meta display handle, so a file row's summary carries more than its bare URL. - `hasNarrowingPositiveTypeRef` is now structure-aware: an `any` narrows only when every branch does, so a disjunction with an unanchored branch keeps the mixed-scope dedup instead of surfacing a card twice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
[Claude Code 🤖] Reviewer heads-up: in the Files changed view the diff may look like it has an indentation problem across all the files. It's a GitHub diff-rendering artifact, not a real issue and not specific to any file type — the files are all consistent 2-space indentation with no tabs and no unusual whitespace. Safe to ignore. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 77b0ebd2d8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| fields: PROJECTION_FIELDS, | ||
| scope, | ||
| }); | ||
| wireQuery.page = { ...wireQuery.page, size: limit }; |
There was a problem hiding this comment.
Enforce the limit after the federated merge
When more than one realm is searched, page.size is applied independently by each realm and combineSearchEntryResults concatenates those pages, so the default limit: 5 can return five rows per readable realm rather than five rows total. Because the default targets every readable realm and each result can include a full readMe, this defeats the tool's output-size bound; trim the merged, re-ranked rows to limit before constructing the result.
Useful? React with 👍 / 👎.
| if (addedRelevanceSort) { | ||
| // The federated merge concatenates per-realm results without re-ranking | ||
| // across realms; relevance rides each entry so the merged page can be. | ||
| // Only the tool's own default ordering is re-imposed here — a caller's | ||
| // explicit sort (which may itself include `_matchRelevance`) stands. |
There was a problem hiding this comment.
Preserve explicit sorts across realms
When query.sort is supplied with multiple realms, each realm sorts only its own page and the federated merge concatenates those pages in realm order; this branch deliberately re-ranks only the automatically added relevance sort. For example, an ascending title query can return Z from the first realm before A from the second, so the advertised optional sort is not honored for the tool's default multi-realm search; merge the per-realm pages according to the caller's sort or otherwise prevent this unsupported combination.
Useful? React with 👍 / 👎.
Background and Goal
The AI's search capability is split across two narrow host tools that each drop something discovery flows need:
SearchCardsByQueryToolsearches all available realms with a pinned card scope and returns only id/title summaries, andSearchCardsByTypeAndTitleToolcannot express aspecTypeor full-textmatchesfilter at all. This PR adds one generalsearch-entrieshost tool — a thin interface over the/_federated-searchentry endpoint (query + realms + scope + limit in, trimmed entry rows out) — and reroutes the two existing tools' descriptions so the model uses them only when it needs live instances to attach, open, or patch.Where to start
packages/host/app/tools/search-entries.ts— the tool: scope validation, mixed-scope dedup, relevance-sort composition, wire translation, flattening, and the result mapping.packages/base/commands/search-entry-result.gts— the input/result card types (re-exported fromcommand.gts).packages/host/tests/integration/tools/search-entries-tool-test.gts— behavior coverage plus unit-style asserts on the exported query-composition helper.Key decisions and non-obvious mechanics
store.searchEntries, notstore.search— the instances path pinsscope: 'cards'and a data-only fieldset;searchEntriespasses scope/fields/page through untouched and already handles auth, federation, and 429 shed-retries.item.cardTitle,item.cardDescription,item.specType,item.readMe,item.ref,item.name): the fieldset stays internal so a caller can never request full serializations.nameis the file-meta display handle, so a file row carries more than its bare URL.readMereturns in full; the size budget is thelimitinput (default 5, max 10). Tool result cards are inlined verbatim into every subsequent prompt turn with no downstream truncation, so the tool bounds its own output — and the result card usescontainsManysummaries, neverlinksToMany(CardDef).'all'with automatic dedup: a card matches both its instance row and its dual-indexed.jsonfile row under the mixed scope, so the filter gainsexcludeCardInstanceFileRows()unless it already carries a kind-narrowing positive type ref (hasNarrowingPositiveTypeRef, now exported from the host search query-builder rather than copied a third time). Narrowing is structure-aware: one narrowing branch narrows anevery, but ananynarrows only when every branch does — a disjunction with an unanchored branch can still match both row kinds._matchRelevancesort is appended only when the filter has a positivematchesterm and no explicit sort (the server 400s the sort without such a term). Merged rows are re-sorted client-side because the federated merge concatenates per realm without cross-realm re-ranking — but only when the tool added the sort itself (composeSearchEntriesQueryreports the addition), so a caller's own ordering, which may legally include_matchRelevance, is never overridden.meta.incompleteand returns the realms that answered. The result card carries that as anincompletefield and the tool description tells the model it means matches may be missing andtotalundercounts.module: '@cardstack/boxel-host/tools/search-entries', name: default. The skill-side declaration lands separately in boxel-skills.Testing
name), scope narrowing and validation, mixed-scope dedup (the narrowing-anchor skip and the partially-anchoredanynon-skip), relevance sorting, its absence withoutmatches, the caller-sort passthrough, limit defaulting/clamping, realm targeting, theincompleteflag default, and the default-export guard.tools | searchsuite, thesearch/query-builderunit suite, and the host tool schema-generation suite (which now includes the new tool viaHostToolClasses) pass.🤖 Generated with Claude Code