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

Reference / Memory

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.

stable2 examples compiled by CIstoragesearchsemanticfull-text

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 Osyrin.Memory;, mark the text you want to search, and then search it the way you already query data.

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

1. The one decision — where you search#

There are two search surfaces, and picking between them is the only real decision. You pick per field, with the scope of [Searchable]:

You want to…SurfaceScopeHow you search
rank one entity's own rows and control the ranking yourselfentity-local field search[Searchable(Entity)]field primitives inside an ordinary query (field search (Matches / TextScore / Similarity))
ask one question across everything and let the engine rankthe shared corpus[Searchable(Memory)]one call, Memory.Search("…") (using Memory (semantic 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).
  • 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). 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).

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#

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] 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#

Nothing here is a new data path. Field search is a query — it obeys the same rules as Querying data, 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#

A field can serve one surface or the other, and an app can use both:

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

Related

[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…

using Memory (semantic search)

Opt into semantic (vector) search over your app's content. `using Memory;` adds a searchable store to the app…

SearchHit

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

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…

Querying data

How you read data in Osy#. You write C# LINQ; it becomes one SQL statement. The rules that follow from that are the…