Retain-time construction of the memory_links graph (Track C1).
Builds directed unit->unit edges so the recall graph and temporal arms have real, precomputed structure to expand over — restoring genuine multi-way RRF fusion instead of recomputing signals per-candidate at query time.
Two link types are built here (mirrors Python Hindsight engine/retain/link_utils.py):
semantic— pgvector ANN. Each new unit is linked to its top-K most similar existing units in the same bank andfact_typewhose cosine similarity is at least@semantic_threshold; weight = cosine similarity. Because the new batch is already committed when this runs, the ANN naturally covers both new-vs-existing and new-vs-new (sibling) neighbours.temporal— each new unit is linked to units whose event time falls within@temporal_window_hours; weight =max(floor, 1 - Δhours/window), capped at@max_temporal_links_per_unitstrongest per unit.
Deferred, explicitly tracked (must not be lost):
caused_bylinks via LlmCore (Track C4).entity-typed unit-grain links (Track C2).
Runs POST-COMMIT (its own reads/writes, outside the retain transaction) so an ANN error can never poison or roll back the retain, and index probes never hold write locks. Best-effort: on any failure it logs and yields 0 links.
Summary
Functions
Build semantic + temporal links for a batch of just-inserted memory units.
Functions
@spec build_links( Ecto.Repo.t(), String.t(), [Foresight.Schemas.MemoryUnit.t()], keyword() ) :: {:ok, non_neg_integer()}
Build semantic + temporal links for a batch of just-inserted memory units.
Returns {:ok, links_created}. Never raises — an internal error is logged and
reported as {:ok, 0} so retain is unaffected.