> ## Documentation Index
> Fetch the complete documentation index at: https://docs.conare.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Search

> Run vector ANN, BM25 keyword, and chronological ranked branches over one filtered row set with a typed property filter DSL and per-branch top_k.

`POST /v1/namespaces/{ns}/search` runs up to three ranked branches over one filtered row set. All branches are optional; at least one is required. `label` and `filter` apply to every branch.

```json theme={null}
{
  "label": "chunk",
  "filter": ["And", [["project", "Eq", "conare"], ["created_at_ms", "Gte", 1720000000000]]],
  "include_props": ["text", "created_at_ms"],
  "branches": {
    "vector":        {"embedding": [0.1, "..."], "top_k": 40},
    "keyword":       {"query": "raw query text", "top_k": 40},
    "chronological": {"attribute": "created_at_ms", "order": "desc", "top_k": 40}
  }
}
```

Response — one independent ranked list per branch, with in-server execution time:

```json theme={null}
{
  "vector":        {"hits": [{"id": "chunk-123", "score": 0.83}], "index": "ivf-rabitq", "server_us": 412},
  "keyword":       {"hits": [{"id": "chunk-9", "score": 14.2}], "mode": "and", "server_us": 200},
  "chronological": {"hits": [{"id": "chunk-7", "value": 1720000000000}], "server_us": 90}
}
```

## Filters

Two shapes are accepted:

* **Single equality**: `{"property": "project", "value": "conare"}` — served straight from posting lists.
* **Array DSL**: `[property, op, value]` with `Eq`, `NotEq`, `Gt`, `Gte`, `Lt`, `Lte`, `In` (array value), composed with `["And", [f1, f2, ...]]` / `["Or", [...]]` up to 16 levels.

Comparison is typed: numbers compare numerically, strings lexicographically (byte order), bools as `false < true`. A type mismatch or absent property matches nothing — including for `NotEq` — never errors. Unknown operators, empty `And`/`Or` lists, and non-array `In` values are `400`s naming the offender.

## Vector branch

Scores are **cosine similarity on L2-normalized vectors, and always exact** — the ANN index (IVF + RaBitQ 1-bit codes) shortlists candidates, then reranks them against full-precision vectors. Options:

| Field        | Default        | Meaning                                                                                                               |
| ------------ | -------------- | --------------------------------------------------------------------------------------------------------------------- |
| `mode`       | `"auto"`       | `"auto"` picks ANN vs exact by modeled cost; `"exact"` forces brute force; `"ann"` requires the index (400 if absent) |
| `nprobe`     | 7% of clusters | Clusters probed — raise for recall, lower for latency                                                                 |
| `oversample` | 4              | Rerank depth multiplier (`top_k × oversample` candidates reranked exactly)                                            |

Filtered vector search returns exactly the ANN-on-the-matching-subset results — the engine widens probing to compensate for selectivity, so filters don't quietly cost you recall.

**Determinism:** for a fixed index state, identical query bytes with identical knobs return byte-identical hit lists — at any thread count or concurrency. Behind the scenes, a continuous sampler replays \~1% of ANN queries against brute-force ground truth and publishes recall; the credential-free `GET /recall-slo` endpoint reports fleet recall health.

## Keyword branch

BM25 (`k1=1.2`, `b=0.75`) over the `text` property. Tokenizer: lowercase, split on every non-alphanumeric character — no stemming, no stopwords (word-tokenizer parity with Turbopuffer).

`match` controls token combination:

* `"auto"` (default) — require all tokens; if that yields fewer than `max(5, top_k/8)` hits, retry as any-token. The response reports which mode ran (`"mode": "and"` / `"or"`).
* `"all"` — every token required.
* `"any"` — ordinary disjunctive BM25.

## Chronological branch

Ranks by any integer property, `asc` or `desc` — the cheap "most recent first" leg of a hybrid query.

## Projecting properties

`include_props` controls what rides along with each hit: omit it for id+score only (zero extra cost), pass a name array to project just those keys, or `true` for the full property map. Projection means a reranker can consume hits without a second lookup roundtrip.

## Turbopuffer-compatible shim

`POST /v2/namespaces/{ns}/query` accepts the Turbopuffer read shape — `{rank_by, filters, top_k, include_attributes}` — and returns `{"rows": [{"id", "$dist", ...}]}` with `$dist` as cosine distance. `rank_by: ["id", "asc"]` with `filters: ["id", "Gt", cursor]` gives cursor pagination/export. Unsupported shapes are explicit `400`s naming the field — never a silently different query.
