# identifier patterns (app.Memory)

> `app.Memory` tells search what an identifier looks like in YOUR data — an order number, a part code, an SKU. Search already spots identifier-shaped tokens in a query and matches them exactly, but it has to guess at the shape; declaring the patterns replaces the guess with an answer, and turns the guessing off.

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

## Summary        {#summary}
Some tokens carry meaning no amount of language understanding can recover: `ORD-004471`, `AB1234X`, `000346`. A
search engine that only understands *meaning* cannot find them reliably, so the platform also matches such tokens
EXACTLY, and weights that match heavily — but only when it is confident the query contains one.

Left alone it decides by shape: a token with a digit and at least four characters. That works, and it is a guess
about your data. `app.Memory` lets you state the shapes instead.

## Signature      {#signature}
```osy syntax
app.Memory = new MemoryConfig {
  Identifiers = [ @"ORD-\d{6}", @"[A-Z]{2}\d{4}[A-Z]" ]
};
```

## Description    {#description}
Each entry is a regular expression. A query is scanned for them, and anything found is matched exactly against the
corpus alongside the ordinary meaning-based search.

**Declaring turns the built-in guessing OFF.** You have answered the question the guess was standing in for, and
running both would put the guesses back on exactly the queries your patterns did not match — the ones you have
implicitly said contain no identifier.

That matters because the shape guess is not perfect on real language. Measured against a public benchmark, it fired
on 6 of 470 questions and five of those were not identifiers at all — `15th`, `10th`, `5-day`, `pre-1920`. Those
now abstain, but a corpus whose codes look like ordinary numbers is still better served by saying so.

**Patterns are checked when you compile.** A pattern that is not a valid regular expression is a compile error
naming it — not a search that fails later, in production, on the first query that happens to reach it.

**They are also checked for SPEED.** Patterns run once per query against text a caller supplied, so a pattern that
can take exponential time on an unlucky input is refused at compile time rather than becoming a way to stall your
app. In practice this rules out backreferences and lookaround; ordinary character classes, quantifiers and anchors
are all fine.

Use `@"…"` for a pattern, as in every example here. Inside `@"…"` a backslash is just a backslash, which is what a
regular expression is made of.

**Removing the declaration puts you back to the built-in behaviour** — the stored patterns go with it.

## Examples       {#examples}
```osy title="declare the shapes your data uses" test app=memory-identifiers
using Osysharp.Memory;

entity Order {
  [MaxLength(64)] string Reference;
  [Searchable(Memory)] string? Notes;
}

app.Memory = new MemoryConfig {
  Identifiers = [ @"ORD-\d{6}", @"[A-Z]{3}-\d{4}" ]
};
```

```osy title="a query naming one is matched exactly, as well as by meaning" test app=memory-identifiers
using Osysharp.Memory;

List<SearchHit> Chase(string question) {
  // "did we ever sort out the packaging fault on ORD-004471?" matches that order's notes on the token
  // itself — not merely on sounding like a packaging complaint.
  return Memory.Search(question, limit: 5);
}
```

## See also       {#see-also}
- [using Memory (semantic search)](https://osysharp.com/reference/memory/search/) — the search this configures
- [How retrieval works](https://osysharp.com/reference/memory/how-retrieval-works/) — why an exact token match is treated differently from a meaning match
- [per-environment config (app.Config)](https://osysharp.com/reference/config/app-config/) — the rest of the `app.` configuration surface
