Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Memory

using Memory (semantic search)

using Memory; … List<SearchHit> Memory.Search(string query, Entity[] about = null, int limit = 20, decimal threshold = 0.5, string keyword = null, Type[] of = null, Entity[] by = null, MemoryKind[] kinds = null, MemoryOrigin origin = null, int related = 0)

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.

stable3 examples compiled by CIstoragesearchsemanticrag

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#

using Osyrin.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#

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:

using Osyrin.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).

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#

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]). 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).

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) instead; Memory.Search is the turnkey call across the whole corpus.

The 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.

  • originMemoryOrigin.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).

  • 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's Via, and a direct hit leaves Via empty — see Memory.Link / Memory.Unlink 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#

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 for the full shape.

Examples#

using Osyrin.Memory;

List<SearchHit> FindArticles(string q) {
  return Memory.Search(q, limit: 5);
}
using Osyrin.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");
}
using Osyrin.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](/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#

Related

SearchHit

One result of a semantic search — the matched text, how relevant it was, where it came from, when it was learned, and…

[Searchable]

Mark a text field searchable. `[Searchable]` gives a String or Markdown property the best relevance search the app can…

field search (Matches / TextScore / Similarity)

Rank an entity's own rows by a query on an [Searchable(Entity)] field. Matches is the keyword filter (bool), TextScore…

Memory.Link / Memory.Unlink

State that two records are related, in your app's own words — "supersedes, because it was renegotiated after the Q2…

Memory.Remember / Memory.Forget

Put a file's text into the app's searchable memory, filed against the record it is about — and take it back out again…

How retrieval works

What happens between `Memory.Search("…")` and the list you get back — indexing, matching, ranking and the two…

Dynamic IN (list.Contains in a query)

Filter a query by membership in a RUNTIME list — list.Contains(e.Column) inside a Where lowers to SQL `= ANY(@param)`…