Skip to content

Scoring & Recall ​

When you call recall, mememory does not simply return the most similar vectors. Results go through a multi-stage pipeline that considers scope, confidence, and recency to produce a ranked list of the most relevant memories.

Scoring Formula ​

Every recall result is scored by:

final_score = similarity x scope_weight x memory_weight x temporal_decay

Each component:

ComponentRangeSource
similarity0.0 - 1.0Cosine similarity between query embedding and memory embedding
scope_weight0.8 - 1.0Determined by the memory's scope level
memory_weight0.1 - 1.0Explicitly set by the user via weight parameter
temporal_decay0.0 - 1.0Exponential decay based on time since last update

Similarity ​

The raw similarity score comes from PostgreSQL's pgvector cosine distance:

sql
1 - (embedding <=> query_vector) AS score

This measures how semantically close the memory content is to the query. A score of 1.0 means identical meaning, 0.0 means completely unrelated.

Scope Weight ​

More specific memories are prioritized over general ones:

ScopeWeightEffect
project1.0No penalty — project memories get full score
global0.820% reduction — global knowledge yields to local

This ensures that a project-level architecture decision outranks a loosely related global fact, even if the global fact has slightly higher vector similarity.

Memory Weight ​

The user-specified weight parameter (default 1.0) acts as a confidence multiplier:

WeightUse case
1.0Confident, current knowledge (default)
0.5Uncertain or tentative beliefs
0.3Partially outdated but still relevant
0.1Auto-downgraded by supersedes mechanism

Weight is the only scoring component directly controlled by the user. Use it to express confidence levels without deleting or hiding memories.

Temporal Decay ​

Newer memories score slightly higher than older ones. The decay function is a gentle exponential:

decay = e^(-lambda * days_since_update)

Where lambda = 0.005. This produces:

AgeDecay factorEffect
1 day0.995Negligible — virtually full score
7 days0.966~3% reduction
30 days0.861~14% reduction
90 days0.638~36% reduction
180 days0.407~59% reduction
365 days0.161~84% reduction

TIP

The decay is intentionally gentle. A 30-day-old memory still retains 86% of its score. This prevents recent but irrelevant memories from drowning out older but highly relevant ones.

The decay is based on updated_at, not created_at. Calling update on a memory resets its temporal decay.

Recall Pipeline ​

The full recall process, step by step:

Step 1: Embed the query ​

The query string is converted to a vector embedding using the configured embedding provider.

Step 2: Fetch candidates ​

A vector similarity search runs against PostgreSQL with a hierarchical WHERE clause (see Scopes). The system fetches 3x the requested limit (minimum 15) to have enough candidates for re-ranking.

Step 3: Filter expired ​

Memories with a TTL that has passed are removed from results.

Step 4: Filter superseded ​

Memories whose ID appears as the supersedes target of another result are removed. This implements belief evolution — only the latest belief in a chain is shown.

Step 5: Score and rank ​

Each remaining memory gets its final score:

final_score = similarity x scope_weight(memory.scope) x memory.weight x temporal_decay(now - memory.updated_at)

Results are sorted by final_score descending.

Step 6: Trim to limit ​

The top N results (default 5) are returned.

Worked Example ​

Suppose you query recall(query="state management", project="match", limit=3):

MemorySimilarityScopeWeightAgeFinal Score
"Uses Zustand for stores" (project:match)0.92project (1.0)1.05d (0.975)0.897
"Prefer Redux for large apps" (global)0.95global (0.8)1.060d (0.741)0.563
"State management is complex" (global)0.88global (0.8)0.52d (0.990)0.349

The project-specific Zustand memory ranks first despite lower raw similarity, because scope weight and recency push it ahead.

Contradiction Detection ​

When calling remember, the system checks for potential conflicts before storing the new memory.

How it works ​

  1. The new content is embedded
  2. A hierarchical search is run against existing memories (same scope hierarchy)
  3. Any existing memory with similarity > 0.75 (75%) is flagged as a potential contradiction

What happens on detection ​

The memory is still stored — contradictions do not block storage. But the response includes:

  • A warning message
  • The list of conflicting memories with similarity percentages
  • Resolution options: keep both, update old, supersede, or delete old
CONTRADICTION DETECTED

New memory:
  [a1b2c3d4] Zustand is better than Redux for small apps

Potentially conflicting memories:
  [e5f6g7h8] (similarity: 82%) Redux is the best state manager

Options:
  1. Keep both — if they are complementary
  2. Update old — call update(id=<old_id>, content=...)
  3. Supersede — call remember(content=..., supersedes=<old_id>)
  4. Delete old — call forget(id=<old_id>)

WARNING

When an agent receives a contradiction warning, it should always ask the user for clarification before choosing a resolution. Never silently keep contradicting memories.

Threshold ​

The contradiction threshold is 0.75 (75% cosine similarity). This value balances between catching real conflicts and avoiding false positives for merely related content.

Released under the MIT License.