Skip to content

Core concepts

Retrieval & ranking

Four signals decide what an agent recalls. This page covers what each one measures, how they are combined, and how to change the balance when the results are not what you want.

The four signals

SignalMeasuresSource
KeywordTerm overlap, length-normalisedBM25 over an inverted index
VectorSemantic closenessThe index metric: cosine, L2, or inner product
RecencyHow fresh the record isExponential decay on a configurable half-life
ImportanceHow much the record mattersThe value you set per record, 0.0 to 1.0

Fusion

Keyword and vector scores live on different scales: BM25 is unbounded, cosine is not. Klyro rescales both onto a common range before combining them, then adds the recency and importance terms.

LINEAR fusion
score = w_keyword·keyword
      + w_vector·vector
      + w_recency·recency
      + w_importance·importance

defaults  0.35        0.50      0.10        0.05

FUSION RRF switches to reciprocal rank fusion, which ranks by position rather than by score. It is steadier when one of the two rankings scored nearly everything the same, which happens with short queries or a narrow corpus.

const hits = await db.memory.query("user:123", {
  text: "shipping preferences",
  vector: [0.10, 0.79, 0.46],
  topK: 5,
  weights: { keyword: 0.2, vector: 0.7, recency: 0.05, importance: 0.05 },
  fusion: "RRF",
  withScores: true,
});

Recency decay

Recency halves every HALFLIFE seconds, defaulting to seven days. A record written moments ago scores 1.0 on this signal; one written a half-life ago scores 0.5; one from four half-lives back scores about 0.06.

klyro
# A day-long half-life for a fast-moving session index
MEM.CREATE session:abc MODE HYBRID DIM 384 HALFLIFE 86400
+OK

# Change it later without touching the records
MEM.CONFIG session:abc HALFLIFE 43200 WEIGHTS 0.3 0.45 0.2 0.05
+OK

Reading a score breakdown

WITHSCORES returns the fused score together with the components that produced it, so a surprising result is a data question rather than a guess.

klyro
MEM.QUERY user:123 TEXT "which database?" FVEC 4 0.1 0.9 0.2 0.4 TOPK 2 WITHSCORES
1) 1) "1"
   2) "User prefers PostgreSQL for backend projects."
   3) 1) "score"      2) "0.9142"
      3) "keyword"    4) "0.7310"
      5) "vector"     6) "0.9981"
      7) "recency"    8) "1.0000"
      9) "importance" 10) "0.8500"

The three query commands

CommandUsesWhen to use it
MEM.SEARCHKeyword onlyExact terms matter and you have no query vector
MEM.VSEARCHVector onlyPure similarity, e.g. deduplicating near-identical memories
MEM.QUERYEither or both, fusedThe default: pass what you have and let the index decide

MEM.QUERY given only TEXT runs a keyword search, given only a vector runs a semantic one, and given both fuses them. That is why application code can call one command whether or not an embedding was available for a given turn.

Return flags

FlagEffect
NOTEXTOmit the record text, when you only need ids and scores
WITHMETAInclude the metadata pairs with each hit
WITHVECInclude the stored vector
WITHSCORESInclude the fused score and its components

Metrics

The metric is fixed at creation. Cosine is the default and the right choice for most embedding models; vectors are normalised at insert so the comparison is a dot product at query time.

METRICComparisonNotes
COSINEAngle between vectorsDefault; magnitudes are normalised away
L2Euclidean distanceSmaller is closer; converted so higher scores rank first
IPInner productFor models trained with an unnormalised objective