Search
Two ways to match
Section titled “Two ways to match”| Mode | How it matches | When it is used |
|---|---|---|
| Full text | English word matching on the message text. The query is read like a web search, so quoted phrases, or and -word work. | Always available. |
| Semantic | Matches by meaning. Each message has a vector (a list of numbers that captures what it means), and the query is turned into one too. Messages whose vectors are close to the query’s are the matches. | Only when embeddings are active, which needs GEMINI_API_KEY and EMBEDDINGS_ENABLED on. |
Search runs both together when it can. Asking for newest-first order turns semantic off, because order by time makes meaning irrelevant. Full-text results are ranked by how well the words match, then by newest.
Semantic search only sees messages that already have a vector. A message posted a moment ago gets one within seconds, but old or backfilled messages wait for the sweep, see AI and embeddings. Until then they can only be found by words. With embeddings off, search uses words alone and the stored vectors go unused.
Merging the two lists
Section titled “Merging the two lists”The caller can pass up to 5 phrasings (different ways of wording the same question; for a question, the model writes them) in one search. Each phrasing makes one full-text list and, when semantic is on, one semantic list. Each list holds the best 50 messages, or the requested limit if larger. With semantic off, a list holds only the requested limit. The lists are combined by reciprocal rank fusion:
- A message earns
1 / (60 + rank + 1)from each list it appears in, where rank starts at 0, and counts once per list. - A message in several lists earns every amount, so agreement rises to the top.
- Results are sorted by score and cut to the limit.
Each result says which mode found it: text, semantic or both.
Topping up a short word list
Section titled “Topping up a short word list”Only for questions: when the strict word match returns fewer messages than a full list, the search adds a looser match. It takes any message containing at least one word of the phrasing, using words of 3 or more letters or digits. It looks only at the newest 2,000 candidates, so a common word cannot rank the whole table, and its extra messages come after the strict ones. Summaries keep the strict match.
When a part fails
Section titled “When a part fails”- If the semantic part fails for any reason (a 4 second timeout for the query embedding, an API error, a database timeout), search logs
[search] semantic leg failed; using full-text only: ...as a warning (a leg is one of the lists above) and returns the full-text results alone. The timeout holds even if the embedding call does not stop when told to. The caller sees no error. - Each database part has a 5 second limit. If a full-text query runs out of time, search logs
[search] text leg timed outand drops only that phrasing. - At most 4 search transactions run on the database at once (one search uses up to two), so a burst of questions cannot take every connection. The rest wait.
Short replies keep their context
Section titled “Short replies keep their context”A reply like “yes, do that” means nothing alone. By default, when a hit is shorter than 60 characters, search attaches the 3 messages before it in the same channel. Deleted messages are skipped, and so are bot messages unless the caller asked to include them. The next older messages take their place, so a hit gets fewer than 3 only when the channel has fewer than 3 such messages before it. Callers can ask for up to 10 earlier messages on any hit, or switch the automatic context off. Questions switch it off.
Summaries do not pass this context to the model. It asks for surrounding messages itself, with get_message_context. Embeddings use the same idea, see AI and embeddings.
Who can see what
Section titled “Who can see what”Every search call must say which channels the asker may see (the allowed channels). Only messages in those channels come back, and the earlier-message context obeys the same list. The search code does not work out permissions itself: the caller must pass a list that already leaves out threads whose parent the asker cannot see.
Always excluded: deleted messages. Search and Recent also leave out bot messages unless asked to include them. Context leaves bot messages out of the messages around the message it is centred on, though that message itself can be a bot message. Member lookup never matches bots. Semantic search also leaves out blank messages and messages whose vector came from a different embedding model than the current one.
What search can do
Section titled “What search can do”The bot has four search operations. Each caller (the code that asks for a search) uses some of them.
| Operation | What it returns | Limit (default, min to max) |
|---|---|---|
| Search | Best matches for a query, with optional channel, author and date filters. | 10, 1 to 15 results (callers can raise the top to 200) |
| Context | A message plus the ones before and after it in its channel. | 5 before and 5 after, 0 to 10 each |
| Recent | Newest messages since a given time, optionally by author or channel. | 20, 1 to 100 (callers can raise the top to 3,000) |
| Lookup | Channels or members whose name contains the text, ignoring case. | Up to 10 |
- Recent also returns one extra row (101, or 3,001 where the cap is raised), so a caller can tell whether more messages exist.
- Lookup lists an exact name match first. For channels, a top-level channel comes before a thread of the same name. For members, the name is the one they used most recently, and the more active author comes first. It covers only the channels the caller may see.
- Each result has the message id, channel id and name, author id and name, sent time and a link to the original message. Text is cut to 500 characters, with an ellipsis where it was cut.
- An empty query, or an empty list of allowed channels, returns nothing.
Who calls it
Section titled “Who calls it”Summaries and questions are the callers. Ticket prompts do not use this search: they search Linear tickets, see Ticket prompts.
A summary uses Search, Context and Recent, always over the channels the reader can see, and not Lookup. Results are kept to the summary period and text is cut to 300 characters before the model sees it. A personal digest uses the same three tools. Search also runs by meaning when embeddings are active, even though the tool describes it as full-text.
| Summary tool | Operation | Limit in a summary |
|---|---|---|
search_messages | Search | 10 by default, 1 to 15 |
get_message_context | Context | 5 before and after by default, 0 to 10 |
list_messages | Recent | 50 by default, 1 to 100 per call |
Questions have wider tools, use the looser word match above, and pass only the channels the asker can view right now. For a public answer these are narrowed to channels everyone who can read the answer channel can view. See Asking the bot questions.
| Question tool | Operation | Limit in a question |
|---|---|---|
search_messages | Search | 1 to 5 phrasings at once, 100 results by default, 1 to 200 |
list_messages | Recent | 1,000 by default, 1 to 3,000 messages in a period |
get_message_context | Context | 5 before and after by default, 0 to 10 |
lookup | Lookup | Finds a member or channel by name, up to 10 |
- AI and embeddings for how vectors are made.
- Summaries for how the model uses these operations.
- Asking the bot questions for how questions use search.
- Coming next for what is planned.