# using Memory (semantic search)

> Opt into semantic (vector) search over your app's content. `using Memory;` adds a searchable store to the app; `Memory.Search("…")` returns the best-matching content as a ranked `List<SearchHit>`, embedding the query for you — no vectors in your hands.

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

## Summary        {#summary}
`using Memory;` turns on semantic search for an app: it adds a searchable content store, and the
`Memory.Search("…")` call returns the content most relevant to a natural-language query as a ranked
`List<SearchHit>`. The query is embedded for you — you write text, never vectors — and results come back ordered
by relevance. Without the `using`, the store and `Memory.Search` don't exist in the app; opting in is what makes
them available.

## Signature      {#signature}
```osy syntax
using Osysharp.Memory;

List<SearchHit> Memory.Search(
    string query,                 // natural-language text (embedded for you)
    Entity[] about = null,        // null/empty = the whole corpus; else memory ABOUT these entities
    int limit = 20,               // maximum hits
    decimal threshold = 0.5,      // maximum distance (closer = smaller)
    string keyword = null,        // optional keyword for hybrid ranking
    Type[] of = null,             // restrict to these entity TYPES
    Entity[] by = null,           // restrict to what these principals AUTHORED
    MemoryKind[] kinds = null,    // restrict to these kinds of memory
    MemoryOrigin origin = null,   // Derived (follows the data) or Authored (somebody chose it)
    int related = 0)              // also search what `about` is LINKED to, this many hops out
```

## Description    {#description}
Semantic search finds content by **meaning**, not exact words: a search for `"how do I get a refund"` surfaces a
passage about *returns and money back* even with no shared keywords. You opt an app in with a single declaration:

```osy syntax
using Osysharp.Memory;
```

That brings three things into the app:

- **a searchable content store** — the app now holds a store of embeddable content. Opted-in apps may query it
  directly like any other data, but `Memory.Search` is the ergonomic default and the one you'll reach for.
- **`Memory.Search(...)`** — the search call itself, described below.
- **`SearchHit`** — the result type each hit comes back as (see [SearchHit](https://osysharp.com/reference/memory/searchhit/)).

None of this exists in an app that doesn't opt in — there is no store to write, nothing to search. The `using` is
the switch.

### What fills the corpus   {#corpus}
`Memory.Search` searches a shared, cross-entity **corpus** — you don't write to it directly. Two authoring surfaces
contribute content to it:

- **`[Searchable(Memory)]` fields** — a `String` or `Markdown` property marked for the corpus is chunked in and
  kept in sync as rows change (see [[Searchable]](https://osysharp.com/reference/memory/searchable/)). This is the usual way to make an entity's text findable.
- **the `semantic =>` entity card** — a composed template that embeds a rendered summary of a whole row into the
  corpus.
- **`Memory.Remember(file, about: row)`** — a stored file's text, filed against the record it is about. Unlike the
  two above it is not automatic: uploading a file indexes nothing until somebody asks for it ([Memory.Remember / Memory.Forget](https://osysharp.com/reference/memory/remember/)).

For entity-local search over a single field — ranking an entity's *own* rows and controlling the ranking yourself —
use the field primitives in [field search (Matches / TextScore / Similarity)](https://osysharp.com/reference/memory/field-search/) instead; `Memory.Search` is the turnkey call across the whole
corpus.

### The call   {#call}
`Memory.Search(query, …)` embeds `query`, ranks the stored content against it, and returns up to `limit` hits
best-first. Every argument past `query` is optional and tunes the search:

- **`query`** *(required)* — the natural-language text to match. You never construct or pass a vector; the engine
  embeds the text.
- **`about`** — zero or more entities the memory should be **about**. Leave it out (or pass `null`/an empty list)
  to search the whole accessible corpus; pass one or more entities to restrict hits to *their* content. The list may
  mix entity types. This narrows the search itself, not the result — so `limit` applies to the entities you named,
  and a hit about them is never crowded out by the rest of the corpus.
- **`limit`** — the maximum number of hits to return (default `20`).
- **`threshold`** — the maximum distance a hit may have; smaller is closer, so a lower threshold is stricter
  (default `0.5`).
- **`keyword`** — an optional keyword. When supplied, ranking fuses semantic similarity with a keyword match
  (hybrid search) so an exact term still pulls its content up. A row matching more of the keyword's words ranks
  higher; matching them all is not required.

  **Pass a distinctive term, not the query.** `keyword` is for the word you know appears verbatim — a part code, a
  clause name, `"warranty"`. Handing it the whole question makes retrieval *worse*, not better: a question is mostly
  ordinary words that appear everywhere, so the keyword half stops discriminating and dilutes the semantic half,
  which was already going to find the answer. If you have no particular term in mind, leave `keyword` out.
- **`of`** — zero or more entity **types**, when you want (say) only deals and contracts. `about` names instances;
  `of` names types, and the two combine.
- **`by`** — the principals whose memory to return. Only *authored* memory has an author — a conversation turn, a
  stated link, a file someone chose to remember — so this excludes derived content by construction.
- **`kinds`** — which kinds of memory to return: a chunk of a property, a section of a document, a conversation
  turn. Use it when a hit's kind changes what you would do with it.
- **`origin`** — `MemoryOrigin.Derived` (it follows the data — the chunks of a `[Searchable]` field, a document's
  sections) or `MemoryOrigin.Authored` (somebody chose it: a conversation turn, a stated link, a file passed to
  [Memory.Remember / Memory.Forget](https://osysharp.com/reference/memory/remember/)).
- **`related`** — how many stated links to follow OUT from `about`, so the search also covers what those entities
  are linked to (0, the default, follows none; at most 3). It needs `about` — following links out of "the whole
  corpus" would reach everything linked to anything, so asking for it without a starting point is a compile error.
  Each hit reached this way carries the link's words in [SearchHit](https://osysharp.com/reference/memory/searchhit/)'s `Via`, and a direct hit leaves `Via`
  empty — see [Memory.Link / Memory.Unlink](https://osysharp.com/reference/memory/link/) for how those words get stated in the first place.

A related hit is **marked, never re-ranked**. Its `Score` means exactly what every other hit's `Score` means; the
fact that a link led you to it is a different claim about relevance, and blending the two into one number would
leave that number meaning neither. So you can show "found because it is similar" and "found because your deal
supersedes it" as the different things they are.

Results are always **ranked** — the list comes back ordered by relevance, most-relevant first — and always
**secured**: a hit you aren't allowed to read never appears, exactly as with an ordinary query.

`Memory.Search` reads content and embeds text, so it is an **effect** (like a data read): its result is held
across a durable suspend/resume without re-running the search.

### The result   {#result}
Each hit is a `SearchHit` carrying the matched text and how relevant it was — `Content`, `Score`
(higher = more relevant), `SourceEntityId` and `SourceEntityType` (which row, and which type, the content came
from), `Kind` (what sort of memory matched), `Distance`, and `Via` (the link that led here, when `related` did).
See [SearchHit](https://osysharp.com/reference/memory/searchhit/) for the full shape.

## Examples       {#examples}
```osy title="search the whole corpus" test app=memory
using Osysharp.Memory;

List<SearchHit> FindArticles(string q) {
  return Memory.Search(q, limit: 5);
}
```

```osy title="scoped + hybrid search" test app=memory
using Osysharp.Memory;

entity Product { string Name; }

List<SearchHit> RelatedTo(Product p, string q) {
  // restrict hits to this product's content, and let an exact keyword pull matches up
  return Memory.Search(q, about: [p], limit: 10, threshold: 0.4, keyword: "warranty");
}
```

```osy title="follow what a deal is linked to" test app=memory
using Osysharp.Memory;

entity Deal { [MaxLength(120)] string Title; [Searchable(Memory)] string Notes; }

List<SearchHit> AroundThisDeal(Deal d, string q) {
  // this deal's own memory, plus memory about whatever it is linked to, one hop out.
  // Anything reached that way comes back with `Via` filled in; anything found directly does not.
  // The LEAD hit is still about this deal — a linked record widens what you get to read, it does not
  // take over the answer. See [How retrieval works](https://osysharp.com/reference/memory/how-retrieval-works/) for what that costs and what it buys.
  return Memory.Search(q, about: [d], related: 1, limit: 10);
}
```

## See also       {#see-also}
- [Memory.Remember / Memory.Forget](https://osysharp.com/reference/memory/remember/) — putting a file's text into the corpus, and taking it back out
- [Memory.Link / Memory.Unlink](https://osysharp.com/reference/memory/link/) — stating the links `related` follows, and what their words mean at each end
- [SearchHit](https://osysharp.com/reference/memory/searchhit/) — the `SearchHit` result type each hit comes back as
- [[Searchable]](https://osysharp.com/reference/memory/searchable/) — the `[Searchable]` attribute; `[Searchable(Memory)]` feeds this corpus
- [field search (Matches / TextScore / Similarity)](https://osysharp.com/reference/memory/field-search/) — entity-local field search (`Matches`/`TextScore`/`Similarity`) when you want to rank one entity's own rows
- [Dynamic IN (list.Contains in a query)](https://osysharp.com/reference/query/dynamic-in/) — filtering an ordinary query by a runtime set
