Skip to content

Search the catalog

{ "name": "search_items", "arguments": { "query": "invoice 2024", "kind": "pdf", "limit": 50 } }
Argument
query words to match; the operator is and, so all of them must appear
kind pdf, image, text, data, xlsx, doc, email, calendar, contact, pkpass, file
limit clamped to 1–200, 50 by default

Omit query and the search becomes a match_all — the most recent items, optionally of one kind.

SearchIndex.search runs a multi_match over three fields:

{ multi_match: { query: query, fields: %w[title^2 locator_key body], operator: "and" } }
Field Holds Analyzer
title the item’s title, boosted ×2 path
locator_key the key within its resource path
body Item#body_text standard

The path analyzer tokenizes on / \ - _ . and whitespace, so 2024/invoices/acme-jan.pdf matches on acme and on invoices.

body is the union of every string extracted by analysis across an item’s references and its children’s, so a scanned PDF matches on its OCR and an email matches on a word that only appears in its attachment. Search quality is downstream of analysis — an item synced but not yet analyzed matches on its path and title alone.

Item.search asks the index for ids and reloads them from Postgres with in_order_of, so results keep their relevance order while every column comes from the system of record.

{ "name": "get_item", "arguments": { "id": "..." } }

Returns every reference, its locator, the per-step analysis results, and body_text truncated to 8,000 characters (Tool::GetItem::EXCERPT).

An item groups references. The same document in a bucket and in Drive is one item with two, not two items.

get_item returns an excerpt, not the file. export_items copies bytes into a storage resource; the browser reads them at /references/:id/content. See Export.

The application never queries the underlying index — only SearchIndex.alias_for(tenant), an alias carrying a term filter on tenant_id that the engine applies:

{ add: { index: index, alias: alias_for(tenant), filter: { term: { tenant_id: tenant.id } } } }

See Tenancy.

ReindexItemsJob writes into a fresh versioned index a page at a time, then SearchIndex.promote! swaps every alias in one update_aliases call. Promotion refuses an index holding fewer documents than it was told to expect.