# Deep Think: Memory Upgrade — Episodic vs Semantic Memory Separation

**Topic**: `episodic_semantic_separation`
**Cycle**: 2
**Model**: gemini-2.5-pro
**Generated**: 2026-02-26T16:31:55.652506+00:00
**Elapsed**: 61.6s
**Thinking tokens**: 16384

---

## Generated Prompt (Phase 1 — Gemini Flash)

AIVA's `memory-lancedb` plugin (`enhanced_index.ts`) currently employs a monolithic `MemoryDB` class and a single LanceDB table, `TABLE_NAME = "memories"`, to store all `MemoryEntry` objects. Each `MemoryEntry` includes `id`, `text`, `vector`, `importance`, `category` (from `MEMORY_CATEGORIES`), and `createdAt`. Hybrid retrieval via `hybridSearch` (weights `HYBRID_WEIGHTS = { vector: 0.4, bm25: 0.3, recency: 0.2, importance: 0.1 }`) and `MMR_LAMBDA = 0.7` apply uniformly across all memories, regardless of their intrinsic nature. `HALF_LIFE_DAYS = 30` and `IMPORTANCE_DECAY_PER_DAY = 0.01` also apply universally. This unified architecture conflates episodic (context-dependent, time-bound events) and semantic (general, enduring facts/knowledge) memory types, leading to several inefficiencies and cognitive misalignments:

1.  **Suboptimal Decay and Retention**: Episodic memories, by nature, should decay faster than semantic ones. The current universal `HALF_LIFE_DAYS` and `IMPORTANCE_DECAY_PER_DAY` are a compromise, leading to either too-fast decay of critical semantic facts or too-slow decay of transient episodic details. This contributes to `consolidateMemories` needing to prune more, or to `memory_recall` returning irrelevant historical events. We estimate P90 recall precision for episodic context could improve by 15-20% if decay rates were differentiated.
2.  **Inefficient Retrieval for Distinct Memory Types**: Semantic memories often benefit from graph-like structures or specialized knowledge bases, enabling inference and structured querying beyond vector similarity. Episodic memories, conversely, rely heavily on temporal and contextual cues. The current `hybridSearch` is optimized for a single vector space, potentially missing crucial structured connections for semantic facts or temporal locality for events. This leads to `memory_recall` sometimes needing multiple iterations or complex queries to reconstruct a coherent narrative from dispersed episodic fragments.
3.  **Lack of Targeted Consolidation**: The `consolidateMemories` function's `DUPLICATE_THRESHOLD = 0.85` and `MERGE_THRESHOLD_LOW = 0.70` apply universally. Semantic memories benefit from robust deduplication and concept generalization, while episodic memories might require summarization or linking rather than strict merging. This generic approach can lead to over-merging of distinct episodic events or under-merging of semantically similar facts expressed differently.
4.  **Ambiguous Store/Recall Routing**: The `memory_store` tool and `autoCapture` mechanism (using `detectCategory`) attempt to categorize, but ultimately store everything into the same `MemoryDB`. Similarly, `memory_recall` queries the single database. This lacks an intelligent, dynamic routing mechanism to direct queries to the most appropriate memory store based on query intent, leading to potentially broader and less focused searches.

We require a dual-store architecture proposal that separates episodic and semantic memory components, optimizes their storage and retrieval, and implements an intelligent routing and fusion layer.

**Propose a dual-store architecture** using two distinct LanceDB tables (e.g., `TABLE_NAME_EPISODIC` and `TABLE_NAME_SEMANTIC`) within the existing `MemoryDB` class structure (or a new `MemoryManager` class).

1.  **Dual Store Design**:
    *   Define two new `MemoryEntry` types, `EpisodicMemoryEntry` and `SemanticMemoryEntry`, possibly with different schemas or additional fields (e.g., `EpisodicMemoryEntry` could include `conversationId`, `turnNumber`).
    *   Outline changes to `MemoryDB` to manage two LanceDB tables. How will `ensureInitialized` be updated?
    *   **Pseudocode/TypeScript Sketch**: Show the class structure for `MemoryDB` or `MemoryManager` with separate `episodicTable` and `semanticTable` properties.

2.  **Differentiated Decay and Consolidation**:
    *   **Episodic Memory**: Propose a faster decay rate for episodic memories (e.g., `EPISODIC_HALF_LIFE_DAYS = 7`, `EPISODIC_IMPORTANCE_DECAY_PER_DAY = 0.05`). `consolidateMemories` should prioritize summarizing or linking similar episodic events rather than aggressive merging, keeping unique event details.
    *   **Semantic Memory**: Propose a slower decay or even no active decay for semantic memories (e.g., `SEMANTIC_HALF_LIFE_DAYS = 90`, `SEMANTIC_IMPORTANCE_DECAY_PER_DAY = 0.005`). `consolidateMemories` should focus on robust semantic deduplication and generalization, potentially leveraging a concept graph (even if simulated via vector clustering) if a full graph database is out of scope.
    *   **Pseudocode/TypeScript Sketch**: Detail how `hybridSearch` will adapt its decay calculation based on the memory type. Show modifications to `consolidateMemories` for type-specific logic.

3.  **Intelligent Routing and Cross-Store Retrieval Fusion**:
    *   Implement an "intent router" that, given a `query` (from `memory_recall` or `autoRecall`), determines whether it's primarily episodic, semantic, or mixed. This could involve keyword checks, `detectCategory` results, or a small LLM call for more nuanced intent.
    *   Design a `fusedSearch` function that calls `hybridSearch` on one or both stores, then intelligently combines and re-ranks results. This might involve different `RECALL_CANDIDATE_COUNT` or `MMR_LAMBDA` per store, and a final cross-store MMR pass.
    *   **Measurable Improvement Target**: Aim to improve P90 precision of `memory_recall` by 10% for mixed queries and reduce `memory_recall` latency by 5% when the query can be confidently routed to a single store.
    *   **Pseudocode/TypeScript Sketch**: Provide a `routeQuery(query: string): 'episodic' | 'semantic' | 'mixed'` function. Outline the `fusedSearch` algorithm, including how scores from different stores are normalized and combined.

4.  **Automatic Routing for Capture**:
    *   Modify the `autoCapture` logic to automatically route new `MemoryEntry` objects to either the episodic or semantic store based on `detectCategory` and `computeImportance`. For instance, `event`, `contact`, `location`, `decision` might lean episodic, while `fact`, `preference`, `skill`, `goal` might lean semantic.
    *   **Pseudocode/TypeScript Sketch**: Show the `agent_end` listener modifications to call a new `storeMemoryIntelligently(fact: string, category: MemoryCategory, importance: number, vector: number[])` function.

Provide a complete implementation roadmap with TypeScript code snippets suitable for direct integration into the `enhanced_index.ts` / `enhanced_config.ts` files.

---

## Thinking Process (Phase 2 — Gemini Pro)

**My Proposed Solution for a Dual-Store Memory Architecture**

Okay, here's my plan to tackle this memory store issue and build a robust, dual-store architecture for `memory-lancedb`. I've broken down the prompt and, after careful consideration, here's how I think we can best proceed.

First, the root of the problem is the conflation of episodic and semantic memories. We need separate stores for each. I'm going to create a system with two LanceDB tables: `episodic` and `semantic`. The existing `MemoryDB` class will be modified internally to manage these tables. This approach keeps the API surface cleaner and minimizes disruption to the current codebase while still creating two stores. The goal is better decay, efficient retrieval, targeted consolidation, and clearer storage and recall.

**Part 1: Dual Store Design**

I'll start in `enhanced_config.ts` by defining the new table names as constants. Then, I'll introduce new `MemoryEntry` types: `EpisodicMemoryEntry` and `SemanticMemoryEntry`. `EpisodicMemoryEntry` will definitely include `conversationId` and `turnNumber` for context. `SemanticMemoryEntry` can stay relatively the same, but for clarity I'll define a type alias to distinguish them. Both types will *extend* the base `MemoryEntry` type using TypeScript's intersection (`&`) functionality.

The key change is the modification of `MemoryDB`. Instead of a single LanceDB table, `MemoryDB` will now manage two LanceDB table objects: `episodicTable` and `semanticTable`. The existing methods, like `addMemory`, will need to be adapted to select the appropriate table.  I'll implement an `ensureInitialized` method that ensures both tables are set up correctly. This should be asynchronous, using `Promise.all` to initialize both tables concurrently.

**Part 2: Differentiated Decay and Consolidation**

Next, in `enhanced_config.ts`, I'll add new constants to control decay rates and consolidation thresholds for each memory type. This is crucial for optimal performance. The `hybridSearch` function will be reworked for this. I'll refactor the existing `hybridSearch` into a private helper function, `_hybridSearch`, which takes decay parameters as inputs. Then, I'll create public methods, `searchEpisodic` and `searchSemantic`, that call `_hybridSearch` with the appropriate decay constants. This is cleaner and more modular.

`consolidateMemories` will also need to be refactored. I'll create a main function, `consolidateMemories`, that then calls two *private* helper functions: `_consolidateEpisodic` and `_consolidateSemantic`. Each helper will implement its type-specific consolidation logic. For episodic memories, the strategy will be summarizing memories to reduce detail, perhaps through linking related events. For semantic memories, it'll be about deduplication and generalization.

**Part 3: Intelligent Routing and Retrieval Fusion**

Now, for intelligent routing. I need to make the system able to select the right store for a given query. I'll start with a mapping that uses existing `MEMORY_CATEGORIES` to new memory types (`'episodic'`, `'semantic'`, or `'mixed'`). I'll implement a `routeQuery` function that uses a simple, but effective, hybrid approach. It first checks for keywords (e.g., "remember when...", "what happened yesterday?" are episodic; "what is...", "tell me about..." are semantic). If there's ambiguity, it can then check the `detectCategory` result.  I can optionally add a call to an LLM if the category mapping or keywords are still inconclusive, but this should be a last resort to minimize latency.

The `fusedSearch` function is where the magic happens. Here's the core logic:

1.  **Routing:** The query is routed to the correct store(s) using `routeQuery`.
2.  **Parallel Search:** If the query is `'mixed'`, both stores will be searched in parallel using `Promise.all`. Otherwise, it searches the designated store only.
3.  **Combination:**  I'll combine the top *K* results from each store. It will likely require some normalization of scores since LanceDB's scores are not comparable across different tables. Initially, I'll combine the lists and let the final MMR pass handle the prioritization, though I may explore more sophisticated normalization in the future.
4.  **MMR Re-ranking:** A final Maximal Marginal Relevance (MMR) pass will be performed on the combined list to ensure diversity and relevance of the final results.

This approach will drive the following measurable improvements: We should see a P90 precision increase of at least 10% for common search queries. Also, the

---

## Deep Think Response

Excellent. This is a well-defined problem statement that identifies clear architectural limitations and provides a strong rationale for a dual-store memory system. The proposed solution will significantly enhance AIVA's cognitive architecture by treating episodic and semantic memories as distinct entities, leading to more accurate recall, efficient storage, and better cognitive alignment.

Here is a comprehensive proposal and implementation roadmap for the dual-store architecture.

---

### **Implementation Roadmap: Dual-Store Memory Architecture for AIVA**

This roadmap outlines the necessary changes to `enhanced_config.ts` and `enhanced_index.ts` to implement a dual-store memory system, separating episodic and semantic memories.

### **Phase 1: Dual Store Design and Configuration**

First, we will update the configuration and the core `MemoryDB` class to manage two distinct LanceDB tables.

#### **1.1. `enhanced_config.ts` Updates**

We'll define new constants for table names, decay rates, and consolidation thresholds.

```typescript
// enhanced_config.ts

// ... existing constants ...

// --- DUAL-STORE CONFIGURATION ---
export const TABLE_NAME_EPISODIC = "memories_episodic";
export const TABLE_NAME_SEMANTIC = "memories_semantic";

// Differentiated Decay Rates
export const EPISODIC_HALF_LIFE_DAYS = 7;
export const EPISODIC_IMPORTANCE_DECAY_PER_DAY = 0.05;
export const SEMANTIC_HALF_LIFE_DAYS = 90; // Longer retention for facts
export const SEMANTIC_IMPORTANCE_DECAY_PER_DAY = 0.005; // Slower decay for facts

// Differentiated Consolidation Thresholds
export const EPISODIC_MERGE_THRESHOLD = 0.75; // Summarize related events
export const SEMANTIC_DUPLICATE_THRESHOLD = 0.90; // Stricter deduplication for facts
export const SEMANTIC_MERGE_THRESHOLD = 0.80; // Generalize similar concepts

// --- ROUTING CONFIGURATION ---
// Mapping from memory category to memory store type
export const CATEGORY_TO_STORE_MAP: { [key in MemoryCategory]: 'episodic' | 'semantic' } = {
  event: 'episodic',
  conversation_turn: 'episodic',
  contact: 'episodic',
  location: 'episodic',
  decision: 'episodic',
  fact: 'semantic',
  preference: 'semantic',
  skill: 'semantic',
  goal: 'semantic',
  concept_explanation: 'semantic',
  meta: 'semantic', // Agent's own state/knowledge
};

// ... existing HYBRID_WEIGHTS, MMR_LAMBDA, etc. may be kept for baseline or adapted per-store
```

#### **1.2. `enhanced_index.ts` Updates**

We'll define new memory entry types and refactor `MemoryDB` to manage two table instances.

```typescript
// enhanced_index.ts
import { Table } from 'vectordb';
import {
  // ... existing imports
  TABLE_NAME_EPISODIC,
  TABLE_NAME_SEMANTIC,
  CATEGORY_TO_STORE_MAP,
  // ... other new config imports
} from './enhanced_config';

// --- TYPE DEFINITIONS ---

// Base Memory Entry remains the same
interface MemoryEntry {
  id: string;
  text: string;
  vector: number[];
  importance: number;
  category: MemoryCategory;
  createdAt: number; // Unix timestamp in seconds
}

// Specialized Memory Entry types
export interface EpisodicMemoryEntry extends MemoryEntry {
  conversationId?: string; // Link to a specific conversation
  turnNumber?: number;      // Position within the conversation
}

export interface SemanticMemoryEntry extends MemoryEntry {
  // Potentially add fields like `source`, `confidence`, or `lastValidatedAt` in the future
}


// --- REFACTORED MemoryDB CLASS ---

export class MemoryDB {
  private lancedb: Connection | null = null;
  private episodicTable: Table | null = null;
  private semanticTable: Table | null = null;
  private embeddingModel: Embeddings;

  constructor(embeddingModel: Embeddings) {
    this.embeddingModel = embeddingModel;
  }

  public async ensureInitialized(): Promise<void> {
    if (this.episodicTable && this.semanticTable) {
      return;
    }

    try {
      const lancedb = await connect('data/lancedb');
      this.lancedb = lancedb;

      const [episodicTable, semanticTable] = await Promise.all([
        this.initializeTable(TABLE_NAME_EPISODIC, 'An episodic memory about a specific event or interaction.'),
        this.initializeTable(TABLE_NAME_SEMANTIC, 'A semantic memory representing a general fact or knowledge.')
      ]);

      this.episodicTable = episodicTable;
      this.semanticTable = semanticTable;

      console.log('Dual-store memory database initialized successfully.');
    } catch (error) {
      console.error('Failed to initialize dual-store memory database:', error);
      throw error;
    }
  }
  
  // Helper to reduce code duplication
  private async initializeTable(tableName: string, tableDescription: string): Promise<Table> {
    const existingTables = await this.lancedb!.tableNames();
    if (existingTables.includes(tableName)) {
      console.log(`Opening existing table: ${tableName}`);
      return await this.lancedb!.open(tableName);
    } else {
      console.log(`Creating new table: ${tableName}`);
      const initialData = [{
          vector: await this.embeddingModel.embedQuery(tableDescription),
          text: tableDescription,
          id: uuidv4(),
          importance: 0.1,
          category: 'meta',
          createdAt: Math.floor(Date.now() / 1000),
      }];
      return await this.lancedb!.create(tableName, initialData);
    }
  }

  // Separate add methods for type safety
  public async addEpisodicMemory(memory: Omit<EpisodicMemoryEntry, 'id'>): Promise<void> {
    if (!this.episodicTable) throw new Error("Episodic table not initialized.");
    const entry: EpisodicMemoryEntry = { ...memory, id: uuidv4() };
    await this.episodicTable.add([entry]);
  }

  public async addSemanticMemory(memory: Omit<SemanticMemoryEntry, 'id'>): Promise<void> {
    if (!this.semanticTable) throw new Error("Semantic table not initialized.");
    const entry: SemanticMemoryEntry = { ...memory, id: uuidv4() };
    await this.semanticTable.add([entry]);
  }

  // ... other methods will be refactored below ...
}
```

---

### **Phase 2: Differentiated Decay and Consolidation**

We will adapt the core logic for search and consolidation to operate specifically on each memory type.

#### **2.1. `enhanced_index.ts` Updates (continued)**

##### **Refactoring `hybridSearch` for Differentiated Decay**

The `hybridSearch` logic will be parameterized to accept decay constants.

```typescript
// enhanced_index.ts (inside MemoryDB class)

interface DecayParams {
  halfLifeDays: number;
  importanceDecayPerDay: number;
}

// A private, generic search function
private async _hybridSearch(
  table: Table,
  queryVector: number[],
  decayParams: DecayParams,
  limit: number,
  filter?: string
): Promise<MemoryEntry[]> {
  const now = Math.floor(Date.now() / 1000);
  const { halfLifeDays, importanceDecayPerDay } = decayParams;

  const search = table
    .search(queryVector)
    .limit(limit * 5) // Fetch more candidates for re-ranking
    .where(filter || '')
    .distanceType("cosine");
    
  // Define a UDF for recency decay calculation right here for clarity
  search.udf("recency", (createdAt: number) => {
    const ageInSeconds = now - createdAt;
    const ageInDays = ageInSeconds / (24 * 60 * 60);
    return Math.pow(0.5, ageInDays / halfLifeDays);
  });
  
  // Define a UDF for importance decay calculation
  search.udf("importance_decay", (createdAt: number, importance: number) => {
      const ageInDays = (now - createdAt) / (24 * 60 * 60);
      return Math.max(0, importance - ageInDays * importanceDecayPerDay);
  });

  // Apply reranking with hybrid scores
  const reranker = LanceDbReranker.create({
      // Using UDFs in the reranking formula
      formula: `(${HYBRID_WEIGHTS.vector} * _score) + (${HYBRID_WEIGHTS.recency} * recency(createdAt)) + (${HYBRID_WEIGHTS.importance} * importance_decay(createdAt, importance))`,
      // BM25 weight is implicitly handled by the LanceDB `search` method if enabled
  });
  
  const results = await search.rerank(reranker).execute();

  return results.map(r => ({
      id: r.id,
      text: r.text,
      vector: r.vector,
      importance: r.importance,
      category: r.category,
      createdAt: r.createdAt,
      _score: r._score, // Keep score for fusion stage
  }));
}
```

##### **Refactoring `consolidateMemories` for Type-Specific Logic**

The main function will now orchestrate consolidation for both stores.

```typescript
// enhanced_index.ts (inside MemoryDB class)

public async consolidateMemories(): Promise<{ consolidated: number; pruned: number }> {
  console.log('Starting dual-store memory consolidation...');
  
  const [episodicResult, semanticResult] = await Promise.all([
    this._consolidateEpisodicMemories(),
    this._consolidateSemanticMemories()
  ]);

  const totalConsolidated = episodicResult.consolidated + semanticResult.consolidated;
  const totalPruned = episodicResult.pruned + semanticResult.pruned;

  console.log(`Consolidation complete. Total consolidated: ${totalConsolidated}, Total pruned: ${totalPruned}`);
  return { consolidated: totalConsolidated, pruned: totalPruned };
}

private async _consolidateEpisodicMemories(): Promise<{ consolidated: number; pruned: number }> {
  if (!this.episodicTable) return { consolidated: 0, pruned: 0 };
  
  // Logic for episodic consolidation:
  // 1. Fetch recent, related memories (e.g., from the same conversationId).
  // 2. Instead of merging, create a new "summary" memory.
  // 3. For example: "During conversation XYZ, we discussed topics A, B, and C."
  // 4. Mark the original, summarized memories for potential archival or lower-priority retrieval, rather than deleting.
  // This is a complex NLP task; a simpler start is to merge very similar turns.
  console.log('Consolidating episodic memories...');
  // ... Implementation would involve querying for clusters of memories
  // based on time and context (conversationId) and applying a summarization model.
  // For now, we can implement aggressive pruning of old, low-importance events.
  const lowImportanceThreshold = 0.2;
  const oldDateThreshold = Math.floor(Date.now() / 1000) - (EPISODIC_HALF_LIFE_DAYS * 3 * 24 * 60 * 60); // 3 half-lives
  
  const toPrune = await this.episodicTable.query()
      .where(`importance < ${lowImportanceThreshold} AND createdAt < ${oldDateThreshold}`)
      .execute();
      
  if (toPrune.length > 0) {
      await this.episodicTable.delete(`id IN (${toPrune.map(r => `'${r.id}'`).join(',')})`);
      return { consolidated: 0, pruned: toPrune.length };
  }
  return { consolidated: 0, pruned: 0 };
}

private async _consolidateSemanticMemories(): Promise<{ consolidated: number; pruned: number }> {
  if (!this.semanticTable) return { consolidated: 0, pruned: 0 };
  
  // Logic for semantic consolidation:
  // 1. Identify highly similar facts using a high similarity threshold.
  // 2. Merge them into a more generalized or comprehensive fact.
  // 3. Delete the redundant, less important originals.
  console.log('Consolidating semantic memories...');
  // This can use a logic similar to the original `consolidateMemories` but with the new thresholds.
  // ... (Implementation of clustering, merging, and deleting)
  // This part is complex and would require a detailed algorithm, but the key is using
  // SEMANTIC_DUPLICATE_THRESHOLD and SEMANTIC_MERGE_THRESHOLD.
  return { consolidated: 0, pruned: 0 }; // Placeholder for the complex logic
}
```

---

### **Phase 3: Intelligent Routing and Cross-Store Retrieval Fusion**

This is the core of the new retrieval system, ensuring queries are handled efficiently and results are combined intelligently.

#### **3.1. `enhanced_index.ts` Updates (continued)**

##### **Query Router and Fused Search**

```typescript
// enhanced_index.ts

type MemoryStoreType = 'episodic' | 'semantic' | 'mixed';

export class MemoryDB {
  // ... existing class properties

  /**
   * Determines the most likely memory store for a given query.
   */
  public async routeQuery(query: string, categoryHint?: MemoryCategory): Promise<MemoryStoreType> {
    if (categoryHint) {
      return CATEGORY_TO_STORE_MAP[categoryHint];
    }
    
    const lowerQuery = query.toLowerCase();
    
    // Simple keyword-based routing
    const episodicKeywords = ['remember when', 'what did we talk about', 'last time', 'yesterday'];
    const semanticKeywords = ['what is', 'tell me about', 'define', 'explain'];

    if (episodicKeywords.some(k => lowerQuery.includes(k))) return 'episodic';
    if (semanticKeywords.some(k => lowerQuery.includes(k))) return 'semantic';

    // Fallback to LLM-based categorization for more nuanced queries
    // const category = await detectCategory(query); // Assuming detectCategory exists
    // if (category) return CATEGORY_TO_STORE_MAP[category];

    // Default to a mixed search if unsure
    return 'mixed';
  }

  /**
   * Performs a search across one or both stores, fusing the results.
   * Target: Improve P90 precision by 10% for mixed queries, reduce latency by 5% for single-store queries.
   */
  public async fusedSearch(query: string, limit: number): Promise<MemoryEntry[]> {
    if (!this.episodicTable || !this.semanticTable) throw new Error("Database not initialized.");

    const queryVector = await this.embeddingModel.embedQuery(query);
    const route = await this.routeQuery(query);
    
    console.log(`Routing query to: ${route}`);

    let candidates: MemoryEntry[] = [];

    if (route === 'episodic') {
      candidates = await this._hybridSearch(this.episodicTable, queryVector, {
        halfLifeDays: EPISODIC_HALF_LIFE_DAYS,
        importanceDecayPerDay: EPISODIC_IMPORTANCE_DECAY_PER_DAY,
      }, limit);
    } else if (route === 'semantic') {
      candidates = await this._hybridSearch(this.semanticTable, queryVector, {
        halfLifeDays: SEMANTIC_HALF_LIFE_DAYS,
        importanceDecayPerDay: SEMANTIC_IMPORTANCE_DECAY_PER_DAY,
      }, limit);
    } else { // 'mixed'
      const [episodicResults, semanticResults] = await Promise.all([
        this._hybridSearch(this.episodicTable, queryVector, {
          halfLifeDays: EPISODIC_HALF_LIFE_DAYS,
          importanceDecayPerDay: EPISODIC_IMPORTANCE_DECAY_PER_DAY,
        }, limit),
        this._hybridSearch(this.semanticTable, queryVector, {
          halfLifeDays: SEMANTIC_HALF_LIFE_DAYS,
          importanceDecayPerDay: SEMANTIC_IMPORTANCE_DECAY_PER_DAY,
        }, limit)
      ]);
      // Simple combination and sorting by score for now. Normalization could be added later.
      candidates = [...episodicResults, ...semanticResults].sort((a, b) => (b._score || 0) - (a._score || 0));
    }

    // Final cross-store MMR re-ranking to ensure diversity in the final results
    if (candidates.length === 0) return [];
    
    const finalResults = await this.applyMMR(candidates);

    return finalResults.slice(0, limit);
  }
  
  // Assume applyMMR and other utilities exist and are adapted if necessary
  private async applyMMR(memories: MemoryEntry[]): Promise<MemoryEntry[]> {
    // ... existing MMR implementation ...
    // It works on a list of MemoryEntry objects, so it should be compatible.
    return maximalMarginalRelevance(memories[0].vector, memories, MMR_LAMBDA, memories.length);
  }
}
```

This sets up the `memory_recall` tool to use `fusedSearch` instead of a direct `hybridSearch`.

---

### **Phase 4: Automatic Routing for Capture**

Finally, we ensure new memories are automatically saved to the correct store.

#### **4.1. `enhanced_index.ts` Updates (final)**

We'll add an intelligent storage function and modify the `autoCapture` listener.

```typescript
// enhanced_index.ts (inside MemoryDB class)

public async storeMemoryIntelligently(
  fact: string,
  category: MemoryCategory,
  importance: number,
  conversationId?: string // Pass context if available
): Promise<void> {
  const storeType = CATEGORY_TO_STORE_MAP[category] || 'semantic'; // Default to semantic
  const vector = await this.embeddingModel.embedQuery(fact);
  const createdAt = Math.floor(Date.now() / 1000);

  console.log(`Storing memory of category '${category}' into '${storeType}' store.`);

  if (storeType === 'episodic') {
    const memory: Omit<EpisodicMemoryEntry, 'id'> = {
      text: fact,
      vector,
      importance,
      category,
      createdAt,
      conversationId,
    };
    await this.addEpisodicMemory(memory);
  } else { // 'semantic'
    const memory: Omit<SemanticMemoryEntry, 'id'> = {
      text: fact,
      vector,
      importance,
      category,
      createdAt,
    };
    await this.addSemanticMemory(memory);
  }
}

// --- AUTO-CAPTURE MODIFICATION ---

// This logic would be in your agent's callback/hook manager
// Example: Modifying the `agent_end` listener for autoCapture

const agentEndListener = async (output: AgentFinish, runId: string) => {
  // ... existing logic to determine if capture is needed ...
  
  // if (shouldCapture) {
    // ... logic to extract text, call computeImportance, and detectCategory ...
    const { fact, importance, category } = await analyzeAndExtractMemory(output.log);
    const conversationId = getConversationIdForRun(runId); // Assume a function to get context
    
    // Use the new intelligent storage function
    await memoryDB.storeMemoryIntelligently(fact, category, importance, conversationId);
  // }
};
```

This completes the roadmap. By implementing these changes, AIVA's memory system will transition from a monolithic store to a more cognitively aligned, efficient, and accurate dual-store architecture.
