# Thread search

Full-text search over prior thread history in one house. Use it: `arbe search "token revocation" --house <id> [-n 20] [--json]`, `POST /api/threads/search`, JS `client.searchThreads(query, houseId)`, or the bot tool `search_threads`. A hit is a thread id and name, the thread's last-updated time, a rank, an opaque offset range, and a match-centered snippet (matched words wrapped in « ») — enough to decide relevance without a second lookup. Snippets are empty for chunks indexed before they stored source text; backfill by replay restores them.

Indexing is a projection of the stream, not a second source of truth: every searchable append fires a throttled, best-effort catch-up (`packages/core/thread-recency.ts`) that replays new stream batches into `thread_search_chunks` via a watermarked, idempotent RPC (`packages/core/search-index.ts`). Chunks are bounded at 500 entries / 100KB and store their source text for snippet extraction. The whole index can be dropped and rebuilt by replay (`packages/core/scripts/backfill-thread-search-index.ts`).

Querying is `websearch_to_tsquery` against a `simple` (unstemmed, so `revokeApiKey` stays findable) tsvector, ranked by `ts_rank` plus a flat boost when the thread title matches, with only the best chunk per thread returned and a `ts_headline` snippet extracted from the chunk's stored source text (`packages/core/thread-search.ts`). Consequences: no kind or author filter, because a chunk blends entries; a phrase spanning a chunk boundary may not match; archived threads surface deliberately.

Surfaces: `apps/cli/src/commands/search.ts`, `apps/www/src/routes/api/threads/search/+server.ts` (membership guard on top of RLS), `packages/core/dispatch/thread-search-tools.ts`. SQL lives in migrations `20260823210000_thread_search_chunks.sql` (table, `catch_up_thread_search_chunk`), `20260824001000_thread_search_read.sql` (`search_thread_chunks`), `20260830130000_thread_search_headlines.sql` (stored chunk source text, thread name/date/snippet hits, title boost), and `20260830131500_thread_search_title_only_matches.sql` (title-only matches pass the gate). The DB contract is proved by `packages/supabase/tests/verify-thread-search-headlines.sql`.

See [threads](threads.md) for what is being indexed, [streams](streams.md) for the append it projects from, and the [CLI](../../cli.md), [HTTP API](../../api.md), and [SDK](../../sdk.md) for search operations.
