| |
| |
| |
| |
|
|
| import { MemoryRecord, ScoredMemory, MemoryConfig, DEFAULT_MEMORY_CONFIG } from './types'; |
|
|
| export function computeDecayFactor(memory: MemoryRecord, now: number = Date.now(), config: MemoryConfig = DEFAULT_MEMORY_CONFIG): number { |
| const ageHours = (now - memory.lastAccessedAt) / (1000 * 60 * 60); |
| const rawDecay = Math.pow(2, -ageHours / config.decayHalfLifeHours); |
| return Math.max(rawDecay, config.minDecayFactor); |
| } |
|
|
| export function computeHeatScore(memory: MemoryRecord, now: number = Date.now(), config: MemoryConfig = DEFAULT_MEMORY_CONFIG): number { |
| const decayFactor = computeDecayFactor(memory, now, config); |
| const frequencyBoost = 1 + Math.log2(1 + memory.accessCount); |
| return memory.importance * frequencyBoost * decayFactor; |
| } |
|
|
| export function applyDecayWeighting(memories: Array<{ record: MemoryRecord; cosineSimilarity: number }>, config: MemoryConfig = DEFAULT_MEMORY_CONFIG): ScoredMemory[] { |
| const now = Date.now(); |
| const RECENCY_WEIGHT = 0.6; |
| return memories.map(({ record, cosineSimilarity }) => { |
| const decayFactor = computeDecayFactor(record, now, config); |
| const decayWeight = RECENCY_WEIGHT * decayFactor + (1 - RECENCY_WEIGHT) * record.importance; |
| const finalScore = cosineSimilarity * decayWeight; |
| return { ...record, cosineSimilarity, decayedScore: decayFactor, finalScore }; |
| }).sort((a, b) => b.finalScore - a.finalScore); |
| } |
|
|
| export async function evictLowHeatMemories(db: any, config: MemoryConfig = DEFAULT_MEMORY_CONFIG): Promise<number> { |
| const countResult = await db.get('SELECT COUNT(*) as count FROM memories'); |
| if (countResult.count <= config.maxMemories) return 0; |
| const allMemories = await db.getAll(`SELECT id, importance, access_count, last_accessed_at, created_at FROM memories`); |
| const now = Date.now(); |
| const scored = allMemories.map((row: any) => { |
| const record: MemoryRecord = { id: row.id, text: '', embedding: [], type: 'semantic', source: 'user', createdAt: row.created_at, lastAccessedAt: row.last_accessed_at, accessCount: row.access_count, importance: row.importance }; |
| return { id: row.id, heat: computeHeatScore(record, now, config), importance: row.importance }; |
| }); |
| const evictionCandidates = scored.filter((m: any) => m.importance < 0.9).sort((a: any, b: any) => a.heat - b.heat).slice(0, config.evictionBatchSize); |
| if (evictionCandidates.length === 0) return 0; |
| const ids = evictionCandidates.map((m: any) => m.id); |
| await db.run(`DELETE FROM memories WHERE id IN (${ids.map(() => '?').join(',')})`, ids); |
| return evictionCandidates.length; |
| } |
|
|
| export function buildAccessUpdateQuery(memoryIds: string[]) { |
| const placeholders = memoryIds.map(() => '?').join(','); |
| return { sql: `UPDATE memories SET last_accessed_at = ?, access_count = access_count + 1 WHERE id IN (${placeholders})`, params: [Date.now(), ...memoryIds] }; |
| } |
|
|