# Workspace search

> A ranked, chunk-aware full-text search over your workspace's file content — the complement to exact-match grep. Use it to find where a topic, concept, or phrase is discussed when you don't know the exact substring; gr…

A ranked, chunk-aware full-text search over your workspace's file content — the
complement to exact-match [`grep`](/docs/kin/guide/tools/#filesystem). Use it to find *where a
topic, concept, or phrase is discussed* when you don't know the exact substring;
`grep` is still the tool for an exact regex or a 1–2 character pattern.

<!-- SOURCE: src/kin/harness/tools/search_workspace.py, src/kin/harness/tools/grep.py, src/kin/harness/search/db.py, src/kin/harness/search/index.py, src/kin/harness/search/chunkers.py, src/kin/harness/search/fusion.py, src/kin/harness/search/indexer.py, src/kin/harness/settings/, container/dashboard/settings_api.py -->

## Default and opt-out

The `search_workspace` tool is **on by default**. To remove it from new sessions:

```toml
# ~/.kin/settings.toml  (or a project .kin/settings.toml — it's project-safe)
search_enabled = false
```

or for a one-off launch:

```bash
KIN_SEARCH_ENABLED=0 uv run kin
```

It is **project-safe**: a local index toggle has no network, egress, or secret
surface, so unlike the containment knobs a project file may turn it off. See
the [`search_enabled` row](/docs/kin/reference/settings-toml/) in the settings
reference for the project-ok/global-only rule this follows.

In the [Outpost Dashboard](/docs/kin/guide/outpost/), the same toggle lives in the **Model &
tuning** card (`search_enabled`, next to `enable_thinking`) — flip it without
touching `settings.toml` by hand. It only affects sessions started *after* the
change; an already-open terminal needs a restart to pick it up. With the default
enabled, the first search may take a moment while the lazy index builds.

## How it works

`search_workspace` builds a small **SQLite FTS5** index (the `trigram` tokenizer,
BM25 ranking) over your workspace's text files — **zero new dependencies** (it's a
standard feature of Python's stdlib `sqlite3`), **no network**, **no egress**.

- **Chunked.** Files are split into chunks — code/text into line windows,
  markdown into heading-delimited sections that carry their `A > B > C` heading
  path — so a hit points at a tight line range (`path:line`) and a markdown hit
  tells you which section it came from.
- **Ranked.** Results come back best-first by BM25 relevance, not file order.
- **Lazy + incremental.** The first search builds the index (you'll see a
  progress note); later searches only re-index files whose `mtime`/content
  changed and drop files that were deleted. A build is bounded by a wall-clock
  budget, so even a huge tree returns promptly (a partial index, with a note).

The index database lives **outside** your workspace, under `~/.kin/index/` (keyed
by the workspace path) — the same discipline [session journals](/docs/kin/guide/sessions/) use.
It is never written into the repo, so it can't be committed or surprise-tracked.

This index is separate from the one behind [Memory](/docs/kin/guide/memory/)'s per-turn
recall — a casual preference must never outrank authoritative repo content,
and vice versa, so the two never share a ranking.

## Arguments

| Argument | Meaning |
|---|---|
| `query` | The search text. Must be **≥ 3 characters** (the trigram floor — shorter strings have no trigram to match; use `grep` for those). |
| `path` | Optional path-prefix filter, relative to the workspace root (e.g. `src/`). |
| `kind` | Optional chunk-kind filter: `markdown` (heading sections) or `text` (code + plain text). Omit to search everything. |
| `limit` | Max results (default 10, max 50). |

## What's indexed (and what isn't)

The crawl reuses `grep`'s discipline and adds a few exclusions, so the index
never contains anything the model couldn't already read — and never anything
sensitive:

- **Excluded:** secret files (`.env`, `~/.ssh`, `.mcp.json`, and the rest of the
  read-deny list), `.gitignore`d paths, binary files (NUL-byte heuristic),
  oversized files, and heavy dirs (`.git`, `node_modules`, `.venv`, …). Excluded
  files appear in **neither** the index **nor** the results.
- **Trust.** Because results are in-workspace content the model already reads
  freely, they carry the **same trust as `grep` output** — they are *not* wrapped
  in the untrusted-content framing that web fetches and out-of-workspace
  `@`-mentions get.

## Relationship to `grep`

| | `grep` | `search_workspace` |
|---|---|---|
| Match | Exact regex / substring | Ranked full-text (BM25, trigram) |
| Order | File order | Relevance, best-first |
| Granularity | Single line | Chunk (line range / heading section) |
| Min length | 1 char | 3 chars |
| Ignored files | Included (except secrets) | Excluded by `.gitignore` |
| Default availability | Always | On (`search_enabled=false` opts out) |

Reach for `search_workspace` when you're searching by *meaning/topic* and don't
know the exact text; reach for `grep` when you know the exact pattern. See the
[Filesystem tools](/docs/kin/guide/tools/#filesystem) section for `grep` and the rest of the
built-in read/write toolset.

> **Semantic search is a later tier**
>
> Today's index is **lexical** (FTS5). A future tier adds optional vector /
> embedding / rerank retrieval on top of the same index for true semantic
> search; it remains deferred, and this page will grow when it lands.
