Skip to main content

Command Palette

Search for a command to run...

Back to Blog
Eryx Memory System: Entity Graphs, Ebisu Curves & Intelligent Retrieval
aimemory-systemsvector-dbqdrantebisurag

Eryx Memory System: Entity Graphs, Ebisu Curves & Intelligent Retrieval

A deep dive into how Eryx implements entity knowledge graphs, Ebisu forget curves, adaptive retrieval, and serendipity injection for AI memory

Introduction

Eryx is an Enterprise AI Collaboration Platform that features a sophisticated multi-layered memory system. Unlike simple RAG (Retrieval Augmented Generation) systems that just chunk and search documents, Eryx implements:

  1. Entity Knowledge Graph — facts linked to canonical entities with temporal validity
  2. Episodic Memory — per-chat session summaries with proper tree branching
  3. Adaptive Forget Curve — Ebisu-style memory decay based on actual access patterns
  4. Intelligent Retrieval — query decomposition, RRF fusion, and intent-based routing

This post dissects every major component, the formulas powering it, and how they all connect.


Architecture Overview

+------------------------------------------------------------------+
|                         User Query                                |
+-----------------------------+------------------------------------+
                            |
                            v
+------------------------------------------------------------------+
|              Query Intent Classification                          |
|  +---------+ +----------+ +------------+ +---------------------+|
|  | DECISION| | FACTUAL  | | EXPLORATORY| | TEMPORAL            ||
|  | vs      | | "what is"| | "tell me   | | "last week"         ||
|  | "decided"| |          | |  about"    | |                     ||
|  +---------+ +----------+ +------------+ +---------------------+|
+-----------------------------+------------------------------------+
                            |
                            v
+------------------------------------------------------------------+
|                 Query Decomposition                              |
|  "what did I decide about auth0"                                 |
|       --> ["auth0 decision", "auth0 choice", "auth0 conclusion"] |
+-----------------------------+------------------------------------+
                            |
                            v
+------------------------------------------------------------------+
|                  Qdrant Vector Search                            |
|  +--------------+ +---------------+ +--------------------+        |
|  | entity_facts  | | episodic_      | | file_chunks +      |      |
|  |               | | summaries      | | memory_embeddings  |      |
|  +--------------+ +---------------+ +--------------------+        |
|              |                                                    |
|              | RRF Fusion (k=60)                                 |
|              v                                                    |
|  +--------------------------------------------------------------+ |
|  |           Reciprocal Rank Fusion                           |  |
|  |  score(d) = SUM 1/(k + rank(d)) for each sub-query       |  |
|  +--------------------------------------------------------------+ |
+-----------------------------+------------------------------------+
                            |
                            v
+------------------------------------------------------------------+
|                  Cohere Rerank (optional)                        |
|  Reranks top results using semantic understanding               |
+-----------------------------+------------------------------------+
                            |
                            v
+------------------------------------------------------------------+
|              Ebisu Forget Curve Filter                          |
|  +--------------------------------------------------------------+ |
|  |  P(t) = stability x 2^(-(t/halfLife)^difficulty)           |  |
|  |                                                             |  |
|  |  Reinforcement on access:                                   |  |
|  |    * halfLife *= 1.5 (capped at 365 days)                 |  |
|  |    * difficulty -= 0.1 (floored at 1.0)                   |  |
|  |    * stability += 0.1 (capped at 2.0)                      |  |
|  +--------------------------------------------------------------+ |
+-----------------------------+------------------------------------+
                            |
                            v
+------------------------------------------------------------------+
|                 Serendipity Injection                           |
|  Thompson-sampled high-confidence facts from unrelated topics     |
|  (prevents echo chambers)                                       |
+-----------------------------+------------------------------------+
                            |
                            v
+------------------------------------------------------------------+
|              Memory Context Assembly                            |
|                                                                  |
|  ## KNOWLEDGE ABOUT USER:                                       |
|  [Sarah] -- confidence: 85%                                    |
|    - Works at Anthropic as ML Engineer                          |
|    - Prefers React over Vue                                     |
|    - Currently learning RL                                      |
|                                                                  |
|  ## RELATIONSHIPS:                                             |
|  - Anthropic -- company_of Sarah                                |
|  - React -- skilled_in Sarah                                   |
|                                                                  |
|  ## MEMORY SPRINKLE:                                           |
|  - [Birthday]: Sarah's birthday is March 15                    |
|                                                                  |
|  ## RECENT DISCUSSIONS:                                        |
|  - 2026-07-18: Discussed auth0 migration...                     |
+------------------------------------------------------------------+

1. Vector Database: Qdrant

1.1 Collections

Eryx uses 6 Qdrant collections, each optimized for a specific content type:

CollectionDimensionsPurpose
entity_facts768Individual facts extracted from chat/file
episodic_summaries768Chat session summaries
entity_context768Aggregated entity context (all facts for one entity)
entity_relations768Semantic relationship vectors
file_chunks768Uploaded file content chunks
memory_embeddings768User memory notes

1.2 HNSW Index Configuration

hnsw_config: {
  m: 16,              // Connections per node — higher = better recall, more memory
  ef_construct: 128,  // Build-time accuracy — higher = more accurate index
  full_scan_threshold: 10000,  // Use exact search for small datasets (<10k vectors)
  on_disk: true,      // Keep HNSW index on disk to reduce RAM
}
  • m=16: Each node connects to 16 nearest neighbors. This is high — typical values are 8-16. More connections improve recall at the cost of memory.
  • ef_construct=128: During index construction, the algorithm explores 128 nearest neighbors. Higher = more accurate index but slower build.
  • on_disk=true: Qdrant stores the HNSW graph on disk instead of RAM. Slower queries but enables running on cheaper instances.

1.3 Cosine Similarity

All collections use Cosine distance:

vectors: {
  size: 768,           // nomic-embed-text produces 768-dim vectors
  distance: "Cosine",   // 1 - (A·B) / (||A|| × ||B||)
  on_disk: true,
}

Cosine similarity is ideal for text embeddings because it measures direction rather than magnitude. Two documents with similar topics but different lengths will have high cosine similarity even if their Euclidean distance is large.

1.4 Recency Boost Formula

When searching, results can be boosted by how recently they were created:

// In applyRecencyBoost():
const recencyFactor = Math.exp(-daysOld / 30) * recencyWeight;
const boostedScore = result.score * (1 + recencyFactor);

Formula:

recencyFactor = e^(-daysOld / 30) × recencyWeight
boostedScore = originalScore × (1 + recencyFactor)
  • 30-day half-life: After 30 days, recencyFactor drops to ~0.37 of its initial value
  • recencyWeight (default 0.3): Controls how much recency affects final score
  • Capped at 1.0 so scores don't exceed maximum

1.5 Freshness Score

Each search result gets a freshnessScore (0-1) indicating how recent it is:

// In computeFreshnessScore():
export function computeFreshnessScore(result, halfLifeDays = 90): number {
  const daysSinceUpdate = (now - updatedAt) / (1000 * 60 * 60 * 24);
  return Math.exp(-daysSinceUpdate / halfLifeDays);
}

Formula:

freshness = e^(-daysSinceUpdate / halfLifeDays)
Days Since UpdateFreshness (halfLife=90)
01.00
300.72
900.37
1800.14
2700.05

Freshness is applied as a minor boost to the final score:

boostedScore = result.score * (1 + freshness * freshnessWeight);
// freshnessWeight default: 0.1

2. Ebisu Forget Curve: Adaptive Memory Decay

2.1 The Ebisu Formula

Eryx implements the Ebisu memory model for fact decay and reinforcement. Unlike simple TTL-based expiration, Ebisu models actual human memory — facts you access frequently become stronger, unused facts decay.

Core Recall Probability Formula:

// In recallProbability():
export function recallProbability(params: EbisuParams, tDays: number): number {
  const { stability, difficulty, halfLife } = params;

  const tHalf = tDays / halfLife;
  const exponent = tHalf ** difficulty;
  const raw = 2 ** -exponent;
  const result = raw * stability;

  return Math.max(0, Math.min(1, result));
}

Formula:

P(t) = stability × 2^(-(t/halfLife)^difficulty)

Where:

  • stability (default 1.0): Resistance to decay. Higher = more durable memory
  • difficulty (default 2.5): Steepness of decay curve. Higher = faster initial drop
  • halfLife (default 30 days): Time until recall drops to ~50% (before stability scaling)

2.2 Parameter Effects

Effect of difficulty on decay:

DifficultyCurve ShapeDescription
1.0Slow decayNearly flat retention
2.5Medium decaySteep initial drop, then flattens
4.0Fast decayVery steep drop

Effect of stability on recall:

StabilityBase RecallDescription
0.550%Low resistance to decay
1.0100%Normal recall
2.0200%High resistance to decay

Effect of halfLife on retention:

HalfLifeRetention SpeedDescription
7 daysFast decayQuick drop-off
30 daysMedium decayStandard retention
90 daysSlow decayLong-term memory

2.3 Reinforcement on Access

When a fact is accessed (retrieved in context), it gets reinforced:

// In reinforce():
const newStability = Math.min(stability + 0.1, 2.0);      // +10%, cap 2.0
const newDifficulty = Math.max(difficulty - 0.1, 1.0);     // -10%, floor 1.0
const newHalfLife = Math.min(halfLife * 1.5, 365);         // ×1.5, cap 365 days

Reinforcement effects:

  • Accessed facts become more durable (increased halfLife)
  • The decay curve becomes shallower (decreased difficulty)
  • Base recall increases (increased stability)

This models the real-world effect of spaced repetition — facts you use often are remembered longer.

2.4 Nightly Decay Job

A background job runs nightly to decay all facts:

// In decayParams():
export function decayParams(params: EbisuParams, daysElapsed = 1): EbisuParams {
  const decayFactor = 0.995 ** daysElapsed;  // 0.5% per day

  return {
    stability: Math.max(params.stability * decayFactor, 0.5),
    difficulty: Math.max(params.difficulty * decayFactor, 1.0),
    halfLife: Math.max(params.halfLife * decayFactor, 7),
  };
}

Decay Factor Formula:

decayFactor = 0.995^daysElapsed

With 0.5% daily decay, after 30 days:

  • stability ≈ 0.86 (14% weaker)
  • difficulty ≈ 0.86 (14% shallower)
  • halfLife ≈ 86% of original

2.5 Soft Delete Threshold

Facts are soft-deleted when:

  • Confidence < 0.25 AND
  • Last accessed > 180 days
if (daysSinceAccess > 365 || newConfidence < 0.25) {
  // Soft delete — resurrectable
  await prisma.entityFact.update({
    where: { id: fact.id },
    data: { deletedAt: now },
  });
}

2.6 Resurrection

Soft-deleted facts can be resurrected when a user asks about them:

// In resurrectSoftDeletedFacts():
// If search returns < 3 results, check soft-deleted facts
// that match query keywords and re-activate them

This handles the "I haven't thought about X in months but I'm asking about it now" case — the user clearly cares, so bring it back.


3. Query Intent Classification

3.1 Intent Types

Eryx classifies queries into 6 types to optimize retrieval:

IntentPatternsStrategy
DECISION"what did I decide", "chose", "agreed"High-confidence facts only, quality weighting
FACTUAL"what is X", "who is Y"Entity-based, direct facts
EXPLORATORY"tell me about", "what do you know"Broad search, all collections
TEMPORAL"last week", "in 2024"Time filters, strong recency boost
COMPARATIVE"X vs Y", "difference between"Multi-entity, expanded limit
RELATIONSHIP"works at", "related to"Graph traversal

3.2 Classification Algorithm

Pattern matching with LLM fallback:

// Pattern scoring
const scores: Map<QueryIntent, number> = new Map();

for (const [intent, patterns] of Object.entries(INTENT_PATTERNS)) {
  let matchCount = 0;
  for (const pattern of patterns) {
    if (pattern.test(trimmed)) {
      matchCount++;
    }
  }
  if (matchCount > 0) {
    scores.set(intent, matchCount);
  }
}

// Best intent
const bestIntent = Array.from(scores.entries()).reduce((a, b) =>
  a[1] > b[1] ? a : b
)[0];

const confidence = Math.min(0.9, 0.4 + matchCount * 0.15);

If no pattern matches, falls back to LLM classification with gpt-4.1-mini.

3.3 Intent-Based Retrieval Strategies

// In getRetrievalStrategy():
switch (intent) {
  case QueryIntent.DECISION:
    return {
      collections: ["entity_facts"],
      scoreThreshold: 0.2,      // Low threshold — confidence matters more
      limit: 5,                 // Top facts only
      applyQualityWeighting: true,
      rerankAfterFusion: true,
    };

  case QueryIntent.EXPLORATORY:
    return {
      collections: ["entity_facts", "file_chunks", "memory_embeddings"],
      scoreThreshold: 0.1,      // Lower threshold — cast wide net
      limit: 20,               // More results
      applyQualityWeighting: false,
      rerankAfterFusion: true,
    };

  case QueryIntent.TEMPORAL:
    return {
      collections: ["entity_facts", "file_chunks", "memory_embeddings"],
      scoreThreshold: 0.15,
      limit: 10,
      applyTemporalFilter: true,
      scoreBoost: { recency: 0.8 },  // Strong recency boost
    };
}

4. Query Decomposition & RRF Fusion

4.1 When to Decompose

Queries are decomposed when they contain:

  • Decision patterns: "decided", "chose", "agreed", "concluded"
  • Comparison patterns: "vs", "versus", "compared", "difference"
  • Multiple entities: "X and Y"

4.2 Decomposition Examples

Input: "what did I decide about the auth0 migration"
Output: ["auth0 migration decision", "auth0 migration choice", "auth0 migration conclusion"]

Input: "how does react compare to vue"
Output: ["react", "vue", "react vs vue comparison"]

Input: "tell me about Anthropic and OpenAI"
Output: ["Anthropic", "OpenAI"]

4.3 Reciprocal Rank Fusion (RRF)

When multiple sub-queries return results, they're fused using RRF:

// In fuseResultsWithRRF():
const RRF_K = 60;

for (const [subQuery, results] of subQueryResults) {
  for (const { item, rank } of results) {
    const rrfScore = 1 / (RRF_K + rank);

    if (existing) {
      existing.fusedScore += rrfScore;  // Accumulate across sub-queries
    } else {
      itemScores.set(item, { fusedScore: rrfScore, subQueryScores });
    }
  }
}

RRF Formula:

score(d) = Σ 1/(k + rank(d)) for each sub-query
k valueEffect
k=0Pure rank — first position gets 1.0, second gets 0.5
k=60Smoothed — first gets 0.016, tenth gets 0.014
k=infinityIgnores rank, pure frequency

Why k=60? It provides enough smoothing to not over-weight the first position, but enough discrimination to still prefer higher-ranked results.

4.4 RRF Visualization

Query: "auth0 migration decision"
Sub-query results:
  SubQ1: [A, B, C, D, E]
  SubQ2: [B, F, A, G, H]
  SubQ3: [A, C, B, I, J]

Item A: rank in Q1=0, Q2=2, Q3=0 -> 1/(60+0) + 1/(60+2) + 1/(60+0) = 0.0331
Item B: rank in Q1=1, Q2=0, Q3=2 -> 1/(60+1) + 1/(60+0) + 1/(60+2) = 0.0330
Item C: rank in Q1=2, Q2=N/A, Q3=1 -> 1/(60+2) + 0 + 1/(60+1) = 0.0327

Final order: A > B > C > D > E > F > ...

5. Cohere Rerank Integration

After RRF fusion, Eryx can apply Cohere Rerank 3 for semantic reordering:

// In rerankEntityFacts():
const response = await fetch("https://api.cohere.ai/v2/rerank", {
  method: "POST",
  headers: { Authorization: `Bearer ${ragConfig.cohereKey}` },
  body: JSON.stringify({
    query,
    documents: facts.map(f => ({ text: f.content })),
    topN: facts.length,
    model: "rerank-english-v3.0",
    return_documents: false,
  }),
});

Flow:

  1. Get initial results from Qdrant (fast vector search)
  2. Reorder with Cohere (semantic understanding)
  3. Take top results

Caching: Results cached for 60 seconds per userId + queryHash to avoid redundant API calls.


6. Serendipity Injection

6.1 The Echo Chamber Problem

Pure relevance retrieval tends to return the same types of facts, creating an echo chamber. Eryx injects "serendipity facts" — high-confidence facts from unrelated topics — to surface forgotten knowledge.

6.2 Thompson Sampling for Injection Probability

The injection probability is learnable per user via Thompson sampling:

// In learnable-serendipity.service:
// Maintain Beta(alpha, beta) distribution per user
// Sample to decide whether to inject serendipity facts
// Update distribution based on user engagement

6.3 Serendipity Selection

// In getSerendipityFacts():
const oldFacts = await prisma.entityFact.findMany({
  where: {
    entityId: { notIn: recentEntityIds },  // NOT recently discussed
    confidence: { gte: 0.7 },             // Only high-confidence
    deletedAt: null,
  },
  orderBy: { confidence: "desc" },
  take: 2,  // Max 2 injections
});

Rules:

  • Only for exploratory intents (not decision/factual/comparative)
  • Minimum 3 entities already retrieved
  • Max 2 facts injected
  • Only from entities NOT in current conversation

7. Adaptive Spatial Index

7.1 The Problem with Fixed Partitions

Traditional vector DBs use fixed partitions. Eryx implements learnable routing — the system learns which entity types are relevant to which queries.

7.2 How It Works

// In adaptive-spatial-index.service:
// 1. Record query + entity types that matched
// 2. Build usage statistics over time
// 3. On new query, predict likely entity types
// 4. Only search relevant partitions

const likelyTypes = await getSearchPartitions(query);
// → ["PERSON", "SKILL"] for "who works on ML"

if (likelyTypes.length > 0 && likelyTypes.length < 8) {
  const entityIds = await prisma.entity.findMany({
    where: { userId, type: { in: likelyTypes } },
    select: { id: true },
    take: 500,
  });
  // Search only these entity partitions
}

Query → Entity Type Examples:

"who knows Rust" → [PERSON, SKILL]
"Auth0 migration" → [PROJECT, COMPANY]
"my presentation" → [EVENT, PROJECT]

8. Memory Context Assembly

8.1 Token Budget Architecture

Total context budget: 16,000 tokens

ComponentMax Tokens
System promptAdaptive
Memory RAG1,500
Project RAG3,000
File content4,000
User-selected memories1,500

8.2 Adaptive Context Windows by Intent

const CONTEXT_WINDOW_TOKENS = {
  decision: { maxTokens: 2000, priorityOrder: ["entityFacts", "relations", "episodic", "chunks"] },
  exploratory: { maxTokens: 3000, priorityOrder: ["entityFacts", "relations", "chunks", "serendipity"] },
  temporal: { maxTokens: 2000, priorityOrder: ["episodic", "entityFacts", "relations", "chunks"] },
};

8.3 Prompt Format


## KNOWLEDGE ABOUT USER:
[Sarah] — confidence: 85%
  - Works at Anthropic as ML Engineer
  - Prefers React over Vue
  - Currently learning reinforcement learning

## RELATIONSHIPS:
- Anthropic — company_of Sarah
- React — skilled_in Sarah
- Anthropic — uses Claude

## MEMORY SPRINKLE (from older topics):
- [Birthday]: Sarah's birthday is March 15
- [Preference]: Sarah prefers dark mode IDE theme

## RECENT DISCUSSIONS:
- 2026-07-18: Discussed auth0 migration decision...
  Topics: auth0, migration, authentication
- 2026-07-15: Talked about new ML project...
  Topics: ML, project, research

## FROM FILES:
- (architecture.md) The system uses a microservices architecture with...

## FROM NOTES:
- (Project Ideas) Consider using vector database for semantic search...

9. Effective Confidence Calculation

9.1 Formula

// In effectiveConfidence():
export function effectiveConfidence(
  baseConfidence: number,  // Source quality (0-1)
  params: EbisuParams,    // Ebisu parameters
  lastAccessedAt: Date
): number {
  const tDays = (Date.now() - lastAccessedAt.getTime()) / (1000 * 60 * 60 * 24);
  const recall = recallProbability(params, tDays);
  return baseConfidence * recall;
}

Formula:

effectiveConfidence = baseConfidence × P(t)

Where P(t) is the Ebisu recall probability.

9.2 Example Calculation

baseConfidence = 0.8  (high-quality file source)
stability = 1.0
difficulty = 2.5
halfLife = 30 days
lastAccessed = 10 days ago

tDays = 10
recall = 1.0 × 2^(-(10/30)^2.5)
       = 1.0 × 2^(-0.163)
       = 1.0 × 0.893
       = 0.893

effectiveConfidence = 0.8 × 0.893 = 0.714

10. Data Flow: End-to-End

10.1 Chat Request Flow

User Message
    │
    ▼
app/api/chat/route.ts (POST)
    │
    ├──► Rate limit check
    ├──► Authenticate
    │
    ├──► buildMessages()
    │    │
    │    ├──► getChatContext() → summarize.service.ts
    │    │    └──► Incremental summarization if needed
    │    │
    │    ├──► retrieveContext() → rag.service.ts
    │    │
    │    └──► getMemoryContext() → memory-v2.service.ts
    │         │
    │         ├──► classifyQueryIntent() → query-intent.service.ts
    │         │    └──► Returns: DECISION, FACTUAL, EXPLORATORY, etc.
    │         │
    │         ├──► decomposeQuery() → query-decompose.service.ts
    │         │    └──► ["sub1", "sub2", "sub3"] or single query
    │         │
    │         ├──► embedText() → nomic-embed-text
    │         │
    │         ├──► getSearchPartitions() → adaptive-spatial-index.service.ts
    │         │    └──► Returns likely entity types
    │         │
    │         ├──► searchEntityFacts() → Qdrant
    │         │    ├──► RRF fusion of sub-query results
    │         │    ├──► Cohere Rerank (optional)
    │         │    ├──► Keyword hybrid boost
    │         │    └──► Temporal validity filter
    │         │
    │         ├──► resurrectSoftDeletedFacts() if sparse results
    │         │
    │         ├──► reinforce() → forget-curve.service.ts (fire-and-forget)
    │         │
    │         ├──► getSerendipityFacts() (Thompson sampling)
    │         │
    │         ├──► getRelationsForEntities() → Prisma
    │         │
    │         ├──► searchFileChunks() + searchMemoryEmbeddings() → Qdrant
    │         │
    │         └──► assembleMemoryPrompt()
    │              └──► Token-budget-aware assembly by intent
    │
    ├──► streamText() → Anthropic/OpenAI
    │
    ├──► Process tool calls → MCP executor
    │
    └──► Queue (fire-and-forget):
         ├──► queueFactExtraction() → entity-fact.service.ts
         └──► deductCredits()

10.2 Fact Extraction Pipeline

After Chat Response:
    │
    ▼
queueFactExtraction() → BullMQ job queue
    │
    ▼
Worker: extractEntities()
    ├──► GPT-4.1-mini extracts entities
    ├──► resolveEntities() (name dedup via nameHash)
    └──► Jaccard similarity on aliases (threshold 0.5)
    │
    ▼
Worker: extractFacts()
    ├──► GPT-4.1-mini extracts facts per entity
    └──► Check for conflicts
    │
    ├──► No conflict → saveEntityFact() → Prisma
    │    │
    │    ├──► indexEntityFact() → Qdrant
    │    └──► reinforce() → Ebisu params initialized
    │
    └──► Conflict → logMemoryConflict() → user resolves
    │
    ▼
updateEntityBaseConfidence()
    └──► Avg of all fact confidences

11. Key Differentiators from Simple RAG

FeatureSimple RAGEryx
Memory modelFlat chunksEntity knowledge graph
Forget curveTTL expirationEbisu adaptive decay
Query handlingSingle queryDecomposition + RRF
Result fusionSingle searchMulti-collection RRF
Echo chamberNo protectionSerendipity injection
Entity resolutionNoneName hashing + fuzzy match
Implicit relationsNoneDERIVES inference
Spatial indexingFixedLearnable routing

12. Configuration Reference

12.1 Ebisu Parameters

const DECAY_CONFIG = {
  initialHalfLife: 30,        // days
  initialDifficulty: 2.5,
  initialStability: 1.0,
  minStability: 0.5,
  maxStability: 2.0,
  minConfidenceThreshold: 0.25,
  reinforcementMultiplier: 1.5,
  difficultyDecayRate: 0.1,
  stabilityBoostRate: 0.1,
  minDifficulty: 1.0,
  maxDifficulty: 4.0,
  maxHalfLife: 365,           // cap at 1 year
  softDeleteAfterDays: 365,
};

12.2 Vector Search Parameters

const HNSW_CONFIG = {
  m: 16,
  ef_construct: 128,
  full_scan_threshold: 10000,
};

const SEARCH_CONFIG = {
  scoreThreshold: 0.7,        // cosine similarity threshold
  recencyWeight: 0.3,         // 30% recency boost
  freshnessWeight: 0.1,        // 10% freshness boost
  freshnessHalfLife: 90,       // days
};

12.3 RRF Parameters

const RRF_K = 60;             // smoothing parameter
const MAX_SUB_QUERIES = 5;
const MIN_QUERY_LENGTH_FOR_DECOMPOSITION = 15;

Conclusion

Eryx's memory system goes far beyond simple document retrieval. By combining:

  1. Entity knowledge graphs with temporal validity and conflict detection
  2. Ebisu forget curves that model actual human memory decay
  3. Query intelligence with decomposition, intent routing, and RRF fusion
  4. Serendipity injection to prevent echo chambers

The system delivers context that's both relevant (accessed recently, high confidence) and surprising (forgotten facts from unrelated topics).

All the formulas, thresholds, and parameters are tunable — the architecture is built for production at scale while remaining flexible for different use cases.