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

Reference / Memory

Memory.Link / Memory.Unlink

using Memory; … bool Memory.Link(Entity a, Entity b, LinkKind as = LinkKind.CrossReference, string label = null, string reason = null, string reverseLabel = null, string reverseReason = null); int Memory.Unlink(Entity a, Entity b)

State that two records are related, in your app's own words — "supersedes, because it was renegotiated after the Q2 review". Every search hit about either record then carries the link, its words and the other record's name, so whoever reads the result can decide whether to go and fetch it.

stable2 examples compiled by CIstoragesearchsemanticrag

Summary#

Memory.Link(a, b, …) records that two records are related and why, in a sentence you write. Search then carries that sentence on every hit about either record (SearchHit.Links), and related: can follow the link to widen a search when you ask it to. Memory.Unlink(a, b) withdraws the claim. Both are available under using Osyrin.Memory;.

Signature#

using Osyrin.Memory;

bool Memory.Link(
    Entity a, Entity b,                  // the two records to relate
    LinkKind as = LinkKind.CrossReference,  // Citation | CrossReference | DerivedFrom | Bookmark | Supersedes
    string label = null,                 // short, read a → b: "superseded by"
    string reason = null,                // why, from a's side: "renegotiated after the Q2 review"
    string reverseLabel = null,          // short, read b → a: "supersedes"
    string reverseReason = null)         // why, from b's side: "replaces the Q1 deal at better terms"

int Memory.Unlink(Entity a, Entity b)    // how many links were removed

Description#

A link is a claim your app makes about two of its records, not a foreign key. Use it when the relationship is something someone decided rather than something the data structure implies: this deal supersedes that one, this invoice cites that contract, this ticket was derived from that report.

Both directions are stated, on purpose#

A link is read from whichever end the reader arrived at, and one sentence cannot be read backwards: "supersedes" from one end is "superseded by" from the other. So the call asks for both sides. If you state only the forward side, the reverse falls back to it — right for a symmetric relationship (CrossReference), and noticeably odd for an asymmetric one, which is the point: the surface asks rather than inventing a sentence for you.

The words ride along with the record#

A link is not indexed as something to find on its own. Instead, every hit about either record carries it — SearchHit.Links gives you the other record's type, id and name, the relationship read from your end, and your sentence:

  • a hit on the old deal carries "superseded by — renegotiated after the Q2 review → Deal D-2026-02";
  • a hit on the new one carries "supersedes — replaces the Q1 deal at better terms → Deal D-2025-11";
  • neither ever shows a reader the sentence written for the other end.

Why not index the sentence itself? Because it is about a PAIR and means nothing without both ends. On its own, "renegotiated after the Q2 review" names nobody — it would match weakly when you searched for the record and ambiguously when you did not. Attached to the record, it arrives in context, and whoever is reading decides whether to fetch the other end. related: is still there for when you want the search itself to travel.

Who stated it#

Memory.Link records the acting principal as the author, because a stated relationship is somebody's claim. It refuses an unauthenticated call, naming the fix, rather than storing a claim nobody made. Memory.Unlink needs no principal: withdrawing a claim is not itself a claim.

What a reader is allowed to see#

A link's words describe a PAIR, so they are shown only to a reader who can read both records. That is checked for you, on every path: a link whose other end you cannot read simply is not there, and a related: hop will not travel through — or quote — a record you cannot see. Links are not a table your app queries; they are reached through these two verbs and through the hits search returns, which is what makes that rule enforceable at all.

A withheld link is not counted. You are never told "3 links (2 hidden)" — that number would itself disclose that two related records exist and that you are not cleared for them. A link you may not see is indistinguishable from a link that was never stated. Where a record has many links, the list is capped for length and SearchHit.LinksElided says how many the cap left out — that count is the cap's alone.

Examples#

using Osyrin.Memory;

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

bool Supersede(Deal older, Deal newer) {
  return Memory.Link(older, newer,
    as: LinkKind.Supersedes,
    label: "superseded by",     reason: "renegotiated after the Q2 review",
    reverseLabel: "supersedes", reverseReason: "replaces the Q1 deal at better terms");
}
using Osyrin.Memory;

int Withdraw(Deal a, Deal b) {
  // either order names the same link; the return says whether there was one to remove
  return Memory.Unlink(a, b);
}

See also#

Related

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

Search

How you make text findable in Osy#. You never touch a vector, an index, or an embedder — you mark a text field…