Search
There are three searches in a workspace and they are different mechanisms, not three spellings of one. Picking the wrong one usually returns nothing, quietly.
6 min
Which one
Start here.
| You want | Use | It matches |
|---|---|---|
| A document containing a word or a name | search | Words. Ranked by BM25. |
| Something the team worked out, however it was phrased | thought-search | Meaning, blended with words. |
| A question with no keyword in it at all | sql | Structure — dates, counts, nulls, joins. |
Document search is keyword search
Which makes it excellent, and sharp.
beehaven search "vectorize pricing" beehaven grep pricing ~
It runs full-text search over the title and the body and ranks with BM25. On a corpus full of distinctive identifiers — a constant name, a file name, a ticket key — that is arguably better than a semantic index: there is no chunking to lose a boundary, no re-embedding on edit, and an exact identifier goes straight to the row.
Note search is stemmed, so crediting finds a document that says credits. That is a property of documents specifically — it is not applied to the thought map, whose scoring is calibrated separately.
Thought search is meaning search
Query it with the idea.
beehaven call thought-search '{"query":"what stops two unrelated ideas being joined"}'The thought map holds the claims a workspace has worked out, and its search blends three signals — a literal match, word overlap, and an embedding comparison — taking the strongest rather than the average. Averaging would drag both good cases toward the threshold via the method that was never going to see them: an identifier match and a clean paraphrase both average to the middle.
So this is the surface to ask a question in your own words. It is also the surface that can answer across time — a decision recorded in one conversation can answer a question asked weeks later in a different one, because there is one map per workspace rather than one per conversation.
SQL is for the questions with no keyword
Read-only, and guarded.
beehaven sql "SELECT title, updated_at FROM notes
WHERE project_id = 'x' AND updated_at > 1757000000000
ORDER BY updated_at DESC"“Which documents in this project changed since Monday”, “how many have no body”, “which project holds the most” — none of these has a word to search for. They are WHERE clauses, and before this existed the only route was to list everything and filter by hand, which stops working the moment the list outgrows the window.
| Allowed | Refused |
|---|---|
One statement, SELECT or WITH | Anything that writes |
| Reading your own workspace tables | Credential-bearing tables |
Schema discovery via pragma_table_info | A bare PRAGMA statement |
Secret-shaped columns come back as [redacted] rather than being removed — an agent that cannot see a column reports that the field does not exist, where one that sees a redaction knows the row holds something it may not read. Rows and cell lengths are capped.
Finding the schema
beehaven sql "SELECT name FROM sqlite_master WHERE type='table'"
beehaven sql "SELECT * FROM pragma_table_info('notes')"One door per surface
Deliberately.
From a shell, SQL is reached through beehaven sql. An agent with a shell reaches it the same way; an agent without one calls the workspace-sql action. There is no second implementation behind either — two ways to do one job is how a model picks at random, and how one path ends up unguarded while the other keeps passing tests.
workspace-sql takes its statement as query. Passing sql — the obvious guess — used to be dropped by validation, and the empty query returned the schema: ok: true, a confident list of tables, and not your answer. sql and statement are accepted aliases now.