search_notes
Locate relevant notes in Kybase by querying with keyword, semantic, or hybrid search. Returns ranked excerpts with folder, tag, and date filters so you can pinpoint information.
Instructions
Search notes. type: "text" (fast), "semantic" (meaning-based), "hybrid" (best, uses RRF). Hybrid is the right default; prefer type=text for exact identifiers, code fragments, or quoted phrases, where FTS beats meaning-matching. Returns short excerpts, not full notes — call get_note on the top 1-2 hits to read them. A query is required, because this ranks text against text: to list or filter notes by folder, tag or recency with no keywords, use list_notes instead. has_more says whether hits exist past the page you got, so a short result is never mistaken for a small vault; read the next page with the next_offset it comes with. It is deliberately a flag and not a total — the only number available here is a capped candidate pool, and for meaning-based matching "how many match" has no answer at all.
A hit in a long note may carry excerpt_offset — where that excerpt sits in the text. Pass it to get_note as offset with a small limit to read around the answer in one call. That is how you read a book or a log: prose with no markdown headings has no outline and no section, so the position is the only way in short of paging from the top.
Read the SECTION, not the note. When a hit carries section, that is the markdown heading its excerpt came from — pass that exact string to get_note's section and you get that part alone (measured on a real 13000-character note: 681 characters). When a hit has no section and its content_length is large, get_note with a small limit still returns the note's FULL headings outline for about a kilobyte — choose a heading from it, then re-read with section. Two small calls beat one 13-60 KB one; pull a whole note only when you genuinely need the whole note. Each hit carries relevance (0..1, how close to the best hit in THIS response) and matched_by (which arms found it). Both describe the response, not the world: relevance orders hits, it does not judge them, and there is deliberately no confidence score. Judge a hit by reading its excerpt.
This is candidate retrieval, not a factual answer, and the search does NOT decide for you whether the vault knows something. Semantic search returns the nearest passages it has; by default nothing is filtered out for being too dissimilar, so an EMPTY result means the index returned nothing at all — and a NON-empty one is not evidence that what you asked about is in there. (An owner may configure a minimum similarity; threshold in the response says whether one is in force, and is null when none is.)
So a hit found ONLY by the semantic arm (matched_by is semantic_score alone) says the passage is ABOUT something similar — never that it confirms what you asked. The two are routinely different: a query about a technology a vault has never used still returns its nearest neighbours with nothing about that technology in them. When a hit is semantic-only and its excerpt does not actually contain what you asked about, the honest reading is "no confirmation found" — say that, or open the note to check. Do not report it as evidence the thing exists. The excerpt is the evidence; the score never is.
text_tier, coverage and exact are observed facts about the text match, shipped when they say something you would not assume. A tier of "or"/"substring" means the strict query found nothing and a looser pass filled in — recall, not confirmation. coverage measures LEXICAL overlap: the share of your query's significant words that occur in the hit, weighted by how rare each is here. A low or zero value does NOT mean irrelevant — a paraphrase or a cross-language match legitimately shares no words with the question, and that is what the semantic arm is for. Read it as "how much of what you typed is literally in there", nothing more. exact: true is the one thing FTS cannot express, and it means exactly this and nothing more: the query occurs as a contiguous, case-insensitive substring of that note (wildcards escaped — A_B does not match AxB). It is set only for a whitespace-free query that still splits into several words — a filename, an identifier, a code symbol, the case where the tokenizer takes one name apart and cannot put it back. Never for a phrase or a question: a note QUOTING your question is not a note answering it. Such hits take the top half of the relevance scale, ranked among themselves by their own text score. Neither tier nor coverage is comparable across different queries, only within one response. Filters: folder_id (or folder_path, the same folder written as a path — no need to look the UUID up first), tag, created_after/before (when a note was made), updated_after/before (when its own content/title/folder/tags last actually changed — a rename elsewhere rewriting a [[link]] to this note does not count) — these are NOT interchangeable. Dates filter, they do not rank: a note edited an hour ago and one untouched for months compete on relevance alone, and nothing here prefers the fresher one. So for "what is the LATEST state of X" this is the wrong first call — list_notes already sorts by recency, newest first, and takes updated_after. Search finds a topic; list_notes finds what changed. A question about the current state of something usually needs both. Every semantic/hybrid response includes threshold/best_score/pending_embeddings so you can tell "nothing was found" from "a configured filter removed it" from "embeddings not generated yet", even when results came back non-empty. Freshness: a hit carrying index_pending:true has an excerpt built from a PREVIOUS version of that note — the note row itself always holds the current text, only its search vectors lag. Call get_note on it (with section, if one is reported) and quote that, not the excerpt, before telling the user what the note says. Response-level pending_embeddings counts how many notes are in that state vault-wide, and stale_generation_chunks counts vectors left over from a previous embedding model, which are excluded from semantic results until reindexed — a non-zero value there explains a thin semantic arm rather than an empty vault. question_echo:true means the note LISTS your question without answering it (an FAQ or agenda of questions); treat it as a pointer to the topic, never as the answer. When reranked:true, prefer type="text" for a term you already know is written in your notes verbatim — an identifier, a filename, a code symbol, a product name. Reranking judges a passage by meaning, and a model that has never seen your vault can rank a passage that reads as more on-topic above the note that literally contains your term — a hit carrying most of your query's words can end up below one carrying far fewer. Only exact:true hits are protected from this. So hybrid remains the right default when you do not know the wording, and text is the better tool when you do — check coverage on a hybrid response to see whether the top hit actually contains what you typed. When the response carries reranked:true, a cross-encoder chose this order instead of rank fusion, and each hit's rerank_score is its best passage's score. That score is a model's opinion about ONE passage of the note, ordering this response only — it is not a confidence value, not comparable between queries, and not evidence the note answers you. reranked:false alongside it means the reranker was asked and did not answer, so you are reading the ordinary fused order. Read the text either way. That model is by far the slowest part of a search, and reranking is off unless an owner turned it on — it is optional and unproven, not an upgrade you are missing. Where it is on, pass rerank:false whenever you want an answer rather than a better ORDER: checking whether a term appears at all, or finding the note holding a value whose shape you already know. Pass explain:true to also see each hit's raw text_score/semantic_score/rrf_score and created_at — only useful for debugging the ranking itself, omitted by default to keep responses short.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| tag | No | Restrict to notes with this tag | |
| type | No | hybrid | |
| limit | No | ||
| query | Yes | ||
| offset | No | Skip this many hits — with has_more in the response, how you read past the first page | |
| rerank | No | Set false to skip the cross-encoder and answer from the fused order — several times faster, and not measurably worse | |
| explain | No | Include raw per-arm scores and created_at for debugging ranking | |
| folder_id | No | Restrict to notes in this folder | |
| folder_path | No | Same restriction by path (e.g. "Projects/Kybase") instead of UUID — that folder itself, not its subfolders | |
| created_after | No | ISO timestamp — only notes created at or after this | |
| updated_after | No | ISO timestamp — only notes whose own content actually changed at or after this | |
| created_before | No | ISO timestamp — only notes created at or before this | |
| updated_before | No | ISO timestamp — only notes whose own content actually changed at or before this |