Using MemCommit

Search and recall

Recall keeps an index in step with the store, so a stale memory is never recalled, and scores hits by relevance, recency, and importance.

from memcommit import Recall

recall = Recall(store, tiers=tiers)           # tiers is optional
hits = recall.search("speed of light", k=3, kinds=["fact"], scopes=["shared"], where={"unit": "km/s"})
for hit in hits:
    print(hit.memory.key, hit.score, hit.relevance, hit.recency, hit.importance)

The score is a weighted sum of relevance (from the index), recency (decay per log record since the memory last changed), and importance. Filters: kinds, scopes, tiers (short and long by default; None searches all, including the archive), and where (body fields). With tiers given to Recall, hits are touched, which is how recall feeds the replacement policies; pass touch=False to leave counts alone.

Indexes

IndexNeedsNotes
LexicalIndexnothingBM25 over the memory's kind, key, and body text. Lowercase terms, a small stopword list dropped, a light stemmer so live, lives, and lived agree.
VectorIndex(embed)an embedding functionCosine similarity. embed maps a list of texts to a list of vectors.
AnnIndex(embed, dimensions)an embedding functionApproximate cosine search by random-hyperplane LSH with multi-probe, for many vectors.

Every index is a Cache: CacheSync admits memories as they go live and evicts them when they stop holding, so a stale or retracted memory is never recalled.

Note

The lexical index matches words, not meaning. A paraphrase that shares no words with the query is not found; use VectorIndex for that. The benchmark in Benchmarks is built so its paraphrases share words with the questions, which favours lexical search.

Search across both tiers

Knowledge.search(query, k, where) searches notes (derived), what was said and done (episodic), or both (all), and returns plain dictionaries. See Notes.