Skip to content

Add a general search-entries host tool over the entry endpoint - #6068

Open
FadhlanR wants to merge 2 commits into
mainfrom
cs-12820-expose-one-general-search-tool-over-the-entry-endpoint-for
Open

Add a general search-entries host tool over the entry endpoint#6068
FadhlanR wants to merge 2 commits into
mainfrom
cs-12820-expose-one-general-search-tool-over-the-entry-endpoint-for

Conversation

@FadhlanR

@FadhlanR FadhlanR commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Background and Goal

The AI's search capability is split across two narrow host tools that each drop something discovery flows need: SearchCardsByQueryTool searches all available realms with a pinned card scope and returns only id/title summaries, and SearchCardsByTypeAndTitleTool cannot express a specType or full-text matches filter at all. This PR adds one general search-entries host tool — a thin interface over the /_federated-search entry 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 from command.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

  • Transport is store.searchEntries, not store.search — the instances path pins scope: 'cards' and a data-only fieldset; searchEntries passes scope/fields/page through untouched and already handles auth, federation, and 429 shed-retries.
  • Fixed sparse projection (item.cardTitle, item.cardDescription, item.specType, item.readMe, item.ref, item.name): the fieldset stays internal so a caller can never request full serializations. name is the file-meta display handle, so a file row carries more than its bare URL. readMe returns in full; the size budget is the limit input (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 uses containsMany summaries, never linksToMany(CardDef).
  • Default scope 'all' with automatic dedup: a card matches both its instance row and its dual-indexed .json file row under the mixed scope, so the filter gains excludeCardInstanceFileRows() 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 an every, but an any narrows only when every branch does — a disjunction with an unanchored branch can still match both row kinds.
  • Relevance: the _matchRelevance sort is appended only when the filter has a positive matches term 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 (composeSearchEntriesQuery reports the addition), so a caller's own ordering, which may legally include _matchRelevance, is never overridden.
  • Partial results are labelled, not hidden: a realm that fails during the federated fan-out doesn't throw — the merge marks the document meta.incomplete and returns the realms that answered. The result card carries that as an incomplete field and the tool description tells the model it means matches may be missing and total undercounts.
  • The tool module default-exports the class so skill frontmatter can declare it as module: '@cardstack/boxel-host/tools/search-entries', name: default. The skill-side declaration lands separately in boxel-skills.
  • Errors propagate to the model instead of collapsing to an empty result, so it can correct query grammar.

Testing

  • New integration suite: 17 tests covering the projection (including the file-row name), scope narrowing and validation, mixed-scope dedup (the narrowing-anchor skip and the partially-anchored any non-skip), relevance sorting, its absence without matches, the caller-sort passthrough, limit defaulting/clamping, realm targeting, the incomplete flag default, and the default-export guard.
  • Existing tools | search suite, the search/query-builder unit suite, and the host tool schema-generation suite (which now includes the new tool via HostToolClasses) pass.

🤖 Generated with Claude Code

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Preview deployments

Host Test Results

    1 files      1 suites   2h 6m 8s ⏱️
4 757 tests 4 743 ✅ 14 💤 0 ❌
4 772 runs  4 758 ✅ 14 💤 0 ❌

Results for commit 77b0ebd.

Realm Server Test Results

    1 files  ± 0    211 suites  +3   1h 20m 34s ⏱️ + 2m 38s
2 775 tests +54  2 775 ✅ +54  0 💤 ±0  0 ❌ ±0 
2 814 runs  +54  2 814 ✅ +54  0 💤 ±0  0 ❌ ±0 

Results for commit 77b0ebd. ± Comparison against earlier commit c636043.

@FadhlanR FadhlanR left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[Claude Code 🤖] (withdrawn)

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>
@FadhlanR

FadhlanR commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

[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.

@FadhlanR
FadhlanR marked this pull request as ready for review September 11, 2026 13:17
@FadhlanR
FadhlanR requested a review from a team September 11, 2026 13:17
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-11T13:21:05.481940Z 77b0ebd Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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 };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +198 to +202
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant