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 removedDescription#
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#
- using Memory (semantic search) — finding the words a link stated, and
related:for following one - SearchHit —
Via, the field a followed link fills in - Search — how text becomes findable in the first place