Skip to content

Search the catalog

{ "name": "search_things", "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 things, 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 thing’s title, boosted ×2 path
locator_key the key within its resource path
body Thing#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 a thing’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 — a thing synced but not yet analyzed matches on its path and title alone.

Thing.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_thing", "arguments": { "id": "..." } }

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

A thing groups references. The same document in a bucket and in Drive is one thing with two, not two things.

get_thing returns an excerpt, not the file. export_things 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.

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