# Search

> How you make text findable in Osy#. You never touch a vector, an index, or an embedder — you mark a text field [Searchable] and search it. The one decision is where you search: an entity's OWN rows with a ranking you compose, or one turnkey call across the whole app. Both give lexical relevance for free and upgrade to semantic automatically.

<!-- id: memory-index · area: memory · stability: stable · html: https://osysharp.com/reference/memory/index/ -->

## Summary        {#summary}
You make text findable by **marking a field `[Searchable]`** — and that is the whole setup. There is no index to
build, no embedding pipeline to run, and no vector that ever reaches your hands. You opt an app in with
`using Osysharp.Memory;`, mark the text you want to search, and then search it the way you already query data.

```osy title="mark a field, search it — no index, no vectors" test app=memory-index
using Osysharp.Memory;

entity Article {
  [MaxLength(200)] string Title;
  [Searchable] string Body;            // that is the entire setup

  security { allow create, read when IsAuthenticated || IsAnonymous; }
}

List<Article> Search(string q) {
  return Article
    .Where(a => a.Body.Matches(q))                  // narrow to candidates (index-backed)
    .OrderByDescending(a => a.Body.TextScore(q))    // rank them — YOUR formula
    .ToList();
}
```

Everything else in this area is two choices layered on top of that: **where** you search, and **which kind of
relevance** you get.

## Description    {#description}

### 1. The one decision — where you search   {#surfaces}
There are two search surfaces, and picking between them is the only real decision. You pick per field, with the
**scope** of [[Searchable]](https://osysharp.com/reference/memory/searchable/):

| You want to… | Surface | Scope | How you search |
|---|---|---|---|
| rank **one entity's own rows** and control the ranking yourself | **entity-local field search** | `[Searchable(Entity)]` | field primitives inside an ordinary query ([field search (Matches / TextScore / Similarity)](https://osysharp.com/reference/memory/field-search/)) |
| ask **one question across everything** and let the engine rank | **the shared corpus** | `[Searchable(Memory)]` | one call, `Memory.Search("…")` ([using Memory (semantic search)](https://osysharp.com/reference/memory/search/)) |

They are not two competing search engines — they are two ergonomics over the same relevance machinery:

- **Field search is a query with extra verbs.** `Prop.Matches(q)`, `Prop.TextScore(q)` and `Prop.Similarity(q)` are
  values you drop into a `Where` and an `OrderBy` — so you filter, order, threshold and weight them with plain
  arithmetic, exactly as you would any other query. You reach for it when you want *this entity's* rows and want to
  decide what "relevant" means. See [field search (Matches / TextScore / Similarity)](https://osysharp.com/reference/memory/field-search/).
- **The corpus is turnkey.** `Memory.Search("how do I get a refund")` embeds the query, searches a shared,
  cross-entity store, fuses lexical and semantic relevance for you, and returns a ranked `List<SearchHit>`
  ([SearchHit](https://osysharp.com/reference/memory/searchhit/)). You reach for it when you want *one answer over the whole app* and want the ranking done
  for you. See [using Memory (semantic search)](https://osysharp.com/reference/memory/search/).

If you write no scope, it is chosen by type — a `String` defaults to entity-local, a `Markdown` field defaults to
the corpus (Markdown is usually long and sectioned, so the corpus is its natural home). Write the scope explicitly
to override.

### 2. The relevance you get is the best available — for free   {#relevance}
Two kinds of relevance exist, and a `[Searchable]` field gives you both when it can:

- **Lexical (full-text)** — keyword matching. It needs no external service, so a `[Searchable]` field is useful the
  instant you deploy.
- **Semantic (vector)** — matching by *meaning*, so `"how do I get a refund"` finds a passage about *returns and
  money back* with no shared keywords. Semantic ranking is active whenever an embedding provider is configured.

The important part is that this is a **degrade, never a fail**: deploy with no embedder and your searchable fields
work as full-text and warn that semantic ranking is inactive; wire an embedder later and every `Full`-mode field
starts ranking by meaning too — **with no change to your code**. The **mode** of [[Searchable]](https://osysharp.com/reference/memory/searchable/) is where you
opt out of the semantic half (`TextOnly`) when keyword search is all you want and you would rather not carry the
per-row vector.

### 3. It is all queries and secured reads   {#security}
Nothing here is a new data path. Field search *is* a query — it obeys the same rules as [Querying data](https://osysharp.com/reference/query/index/), runs in the
database, and sees your uncommitted rows. `Memory.Search` is an ordinary read: a hit you are not allowed to read
never appears, exactly as a filtered query never returns a row you cannot see. There is no separate "search
permission" to configure and nothing extra to reason about — you declared who may read the entity, and search
returns what a read would.

### 4. Putting it together   {#together}
A field can serve one surface or the other, and an app can use both:

```osy title="both surfaces in one app" test app=memory-index
using Osysharp.Memory;

entity Doc {
  [MaxLength(200)] string Title;
  [Searchable(Memory)] string Summary;   // into the shared corpus — turnkey Memory.Search
  [Searchable(Entity)] string Notes;     // entity-local — rank Doc's own rows yourself

  security { allow create, read when IsAuthenticated || IsAnonymous; }
}

// turnkey: one ranked answer across the corpus
List<SearchHit> Ask(string q) {
  return Memory.Search(q, limit: 5);
}

// entity-local: this entity's rows, your ranking
List<Doc> ByNotes(string q) {
  return Doc.Where(d => d.Notes.Matches(q))
            .OrderByDescending(d => d.Notes.TextScore(q))
            .ToList();
}
```

## See also       {#see-also}
- [[Searchable]](https://osysharp.com/reference/memory/searchable/) — the `[Searchable]` attribute: **scope** (entity-local vs corpus) × **mode** (lexical vs +semantic)
- [field search (Matches / TextScore / Similarity)](https://osysharp.com/reference/memory/field-search/) — `Matches` / `TextScore` / `Similarity`: rank an entity's own rows with a ranking you compose
- [using Memory (semantic search)](https://osysharp.com/reference/memory/search/) — `Memory.Search`: one turnkey call over the shared corpus
- [SearchHit](https://osysharp.com/reference/memory/searchhit/) — the `SearchHit` result type `Memory.Search` returns
- [Memory.Link / Memory.Unlink](https://osysharp.com/reference/memory/link/) — `Memory.Link` / `Memory.Unlink`: state why two records are related, in words search can find
- [Memory.Remember / Memory.Forget](https://osysharp.com/reference/memory/remember/) — `Memory.Remember` / `Memory.Forget`: a stored file's text, in and out of the corpus
- [How retrieval works](https://osysharp.com/reference/memory/how-retrieval-works/) — what happens between the call and the list: indexing, matching, ranking, returning
- [Querying data](https://osysharp.com/reference/query/index/) — the querying model field search is built on
