# SearchHit

> One result of a semantic search — the matched text, how relevant it was, where it came from, when it was learned, and what its record connects to. Enough to quote it and say where the quote is from. Returned by Memory.Search as a List<SearchHit>. Available under `using Memory;`.

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

## Summary        {#summary}
`SearchHit` is a single result of a semantic search: the text that matched, a relevance `Score`, the id of the
entity the text came from, and the raw `Distance`. `Memory.Search` returns a `List<SearchHit>` ranked
most-relevant-first. It is available under `using Memory;` and cannot be redeclared by the app.

## Signature      {#signature}
```osy syntax
class SearchHit {
  // what matched
  string Content;          // the human-readable text that matched
  decimal Score;           // relevance — higher is more relevant
  decimal Distance;        // raw vector distance to the query — lower is closer
  MemoryKind Kind;         // a property chunk, a document section, a chat turn
  string? Via;             // the link's words, when `related:` reached this hit — null for a direct hit

  // which record
  Guid SourceEntityId;     // the entity instance this came from (a value, not a live reference)
  string SourceEntityType; // the NAME of the entity type that id belongs to
  string? SourceLabel;     // that record in words — its `semantic` render
  string? SourceDocument;  // the FILE the words are in, when it came from one

  // where, exactly
  string? Section;         // the heading this text sits under
  int? PageFrom;           // first page, when the source has pages
  int? PageTo;             // last page — "45-46" when it straddles a break
  int? SourceOffset;       // where this text starts in the source it was extracted from
  int? MatchOffset;        // where inside Content the match is
  int? MatchLength;        // how much of Content matched

  // when, and on whose say-so
  DateTime? RememberedAt;  // when this was learned
  MemoryOrigin Origin;     // Derived (the record restated) or Authored (somebody chose to remember it)
  Guid? AuthoredBy;        // who — null for Derived, always

  // what it connects to
  MemoryLink[] Links;      // what this record links to, and why
  int? LinksElided;        // how many links were left out for length
}
```

## Description    {#description}
A `SearchHit` is a plain in-memory value describing *why* a piece of content matched, so you can rank, filter, or
display results without re-fetching the source:

- **`Content`** — the text that matched the query.
- **`Score`** — relevance, higher is more relevant. When a `keyword` is supplied to the search, the score is the
  fused (hybrid) relevance; otherwise it is the semantic similarity.
- **`SourceEntityType`** — the NAME of the entity type `SourceEntityId` belongs to. You need both: the id alone
  says which row a hit came from but not which table, so it cannot be loaded, grouped or linked on its own.
- **`Kind`** — what kind of memory matched: a chunk of a `[Searchable]` property, a section of a document, a
  conversation turn. Worth reading when you build a prompt from hits — a recalled conversation turn and a stored
  field are different kinds of claim.
- **`SourceEntityId`** — the id of the entity the content came from. It is a plain `Guid` **value**, not a live
  entity reference — use it to load the full entity when you need it.
- **`Distance`** — the raw vector distance to the query embedding; lower is closer. `Score` is the value to rank
  on; `Distance` is exposed for tuning a `threshold`.
- **`Via`** — why this hit is here at all, when `Memory.Search`'s `related:` reached it by following a stated link
  instead of by being about one of the entities you named. It carries the link's words read in the direction you
  travelled, so a hit found through *your* deal reads "superseded by — renegotiated after the Q2 review" rather than
  the sentence the other end would read. Two hops read as a path (`"… → …"`), and a hit several links reached
  carries each of their reasons. It is **empty for a direct hit**, which is what lets you tell the two apart —
  and the hop is never folded into `Score`, because "similar to your query" and "linked to what you asked about"
  are different claims and one number cannot carry both.

### Saying where it came from   {#attribution}

A quoted fact nobody can check is not much better than an unquoted one, so a hit carries everything needed to
attribute it:

- **`SourceLabel`** — the record in words, rendered from its `semantic` template: *"Contract MSA-2024-11"*, beside
  the `SourceEntityId` a machine can act on. It is read fresh on every search, so renaming a record renames its
  citations immediately — nothing is re-indexed and nothing goes stale.
- **`SourceDocument`** — the file the words are in, when the memory came from one. You usually want this *and*
  `SourceLabel`: "Order SO-1001, p. 45" cannot be checked when the order has four attachments.
- **`Section`** — the heading the text sits under, when the source had headings.
- **`PageFrom` / `PageTo`** — the pages, when the source has pages. A range, because text straddles page breaks.
- **`SourceOffset`** — where the returned text begins in the source it was extracted from. The general form of the
  same question, for sources whose structure is not pages — a transcript's timeline, a source file's lines.
- **`MatchOffset` / `MatchLength`** — where inside `Content` the match actually is.
  `Content.Substring(MatchOffset, MatchLength)` is the sentence that matched, which is what you want to quote or
  highlight when `Content` is a whole page. Empty when the hit is short enough to be its own match.

### When, and on whose say-so   {#provenance}

- **`RememberedAt`** — when this was learned, not when a row was written. Two memories often disagree, and the
  later one is usually right; without a date you cannot tell which is later.
- **`Origin`** — `Derived` means the record restating itself (a `[Searchable]` property's text, a document
  section); `Authored` means somebody chose to remember it (a conversation turn, a file, a stated link). Worth
  reading before you present a hit as fact: *"the contract says"* and *"somebody said"* carry different weight.
- **`AuthoredBy`** — who, for authored memory. Null for `Derived` — asking who wrote a property's chunk is a
  category error, not a missing value.

### What it connects to   {#links}

**`Links`** carries what the hit's record is connected to, and why — each with the target's type, id and label, the
relationship read in the right direction, and the link's own stated words. It is an ANNOTATION, not a second
search: it describes what was already found, so it never changes the ranking and never crowds out a result. That is
the difference between it and `related:`, which genuinely widens the search and is opt-in for that reason.

A record with hundreds of links does not empty them into an answer — the list is capped, and **`LinksElided`** says
how many were left out. A link whose target you are not allowed to read is simply absent, and is never counted.

`SearchHit` is provided by the platform under `using Memory;`. Because `Memory.Search`'s return
contract depends on its exact shape, an app cannot declare its own type named `SearchHit` while the capability is
in use — doing so is a compile error.

## Examples       {#examples}
```osy title="rank and read hits" test app=memory
using Osysharp.Memory;

string BestMatch(string q) {
  var hits = Memory.Search(q, limit: 3);   // already ranked best-first
  if (hits.Count == 0) { return ""; }
  return hits[0].Content;
}
```

```osy title="quote a hit, and say where the quote is from" test app=memory
using Osysharp.Memory;

string Cite(string question) {
  var hits = Memory.Search(question, limit: 1);
  if (hits.Count == 0) { return "nothing on file"; }

  var hit = hits[0];
  var where = hit.SourceLabel;
  if (hit.Section != null) { where = where + " - " + hit.Section; }
  if (hit.PageFrom != null) { where = where + ", p. " + hit.PageFrom; }
  return hit.Content + " (" + where + ")";
}
```

```osy title="follow the thread the hit hands you" test app=memory
using Osysharp.Memory;

string SupersededBy(string question) {
  var hits = Memory.Search(question, limit: 1);
  if (hits.Count == 0) { return ""; }

  foreach (var link in hits[0].Links) {
    if (link.Label == "superseded by") { return link.TargetLabel + ": " + link.Reason; }
  }
  return "";
}
```

## See also       {#see-also}
- [using Memory (semantic search)](https://osysharp.com/reference/memory/search/) — the `Memory.Search` call that returns these hits
- [Memory.Link / Memory.Unlink](https://osysharp.com/reference/memory/link/) — how a link's words are stated, and why each end gets its own
- [How retrieval works](https://osysharp.com/reference/memory/how-retrieval-works/) — why a hit's text is a whole passage and the match is a range inside it
