/** * Memory System — 4 слоя памяти (концепт Animus). * Хранение: SQLite (5 таблиц) + bge-m3 эмбеддинги (cosine-поиск). * * Слои: * short-term → thoughts (recent thoughts, last N) * working → notepads (notepad per contact) * core self → self_observations (накопленная самосознательность) * long-term → long_term + long_term_embeddings (факты/опыт/процедуры/исходы) */ import { EmbeddingClient, cosineSimilarity } from './embedding.js'; import { Database } from './database.js'; export interface Thought { id: string; text: string; createdAt: number; } export interface ContactNotepad { contactId: string; block: string; updatedAt: number; } export interface SelfObservation { id: string; text: string; createdAt: number; tags: string[]; } export interface LongTermMemory { id: string; text: string; type: 'fact' | 'experience' | 'procedure' | 'outcome'; createdAt: number; embedding?: number[]; } export interface MemorySystemConfig { recentCount?: number; dbPath?: string; } export class MemorySystem { private recentCount: number; private db: Database; private embeddingClient: EmbeddingClient | null; constructor(config: MemorySystemConfig = {}, embeddingClient?: EmbeddingClient) { this.recentCount = config.recentCount ?? 10; this.db = new Database(config.dbPath); this.embeddingClient = embeddingClient ?? null; } // === Short-term: последние мысли/опыт/сообщения === addThought(text: string): Thought { const t: Thought = { id: `th-${Date.now()}`, text, createdAt: Date.now() }; this.db.insertThought(t.id, t.text, t.createdAt); return t; } recentThoughts(): Thought[] { const rows = this.db.getRecentThoughts(this.recentCount); return rows.map(r => ({ id: r.id, text: r.text, createdAt: r.created_at })); } // === Working memory: notepad по контактам === updateNotepad(contactId: string, block: string): void { const ts = Date.now(); this.db.upsertNotepad(contactId, block, ts); } getNotepad(contactId: string): string { return this.db.getNotepad(contactId) ?? ''; } // === Core self: накопленная самосознательность === addSelfObservation(text: string, tags: string[] = []): SelfObservation { const o: SelfObservation = { id: `so-${Date.now()}`, text, createdAt: Date.now(), tags }; this.db.insertSelfObservation(o.id, o.text, o.createdAt); return o; } getAllSelfObservations(): SelfObservation[] { const rows = this.db.getAllSelfObservations(); return rows.map(r => ({ id: r.id, text: r.text, createdAt: r.created_at, tags: [] })); } // === Long-term: факты/опыт/процедуры/исходы + эмбедд для векторного поиска === async addLongTerm(text: string, type: LongTermMemory['type']): Promise { const m: LongTermMemory = { id: `lt-${Date.now()}`, text, type, createdAt: Date.now() }; let embedding: number[] = []; if (this.embeddingClient) { embedding = await this.embeddingClient.embed(text); } // Сначала long_term (FK требует), потом embedding this.db.insertLongTerm(m.id, m.text, m.type, m.createdAt); if (embedding.length > 0) { this.db.insertLongTermEmbedding(m.id, embedding); } m.embedding = embedding; return m; } /** Поиск по long-term по тексту через эмбедд (cosine similarity в SQLite). */ async searchLongTerm(query: string, topK = 3): Promise { if (!this.embeddingClient) { const rows = this.db.getLongTerm(this.recentCount); return rows.map(r => ({ id: r.id, text: r.text, type: r.type as LongTermMemory['type'], createdAt: r.created_at })); } const q = await this.embeddingClient.embed(query); const all = this.db.getAllLongTermWithVectors(); const scored = all .map(m => ({ m, score: cosineSimilarity(q, Array.from(m.embedding)) })) .sort((a, b) => b.score - a.score) .slice(0, topK) .map(x => ({ id: x.m.id, text: x.m.text, type: x.m.type as LongTermMemory['type'], createdAt: x.m.created_at, embedding: Array.from(x.m.embedding), })); return scored; } /** Консолидация: перенос недавних мыслей в long-term (по спецификации Animus). */ async consolidate(recent: Thought[]): Promise { for (const t of recent) { // эвристика: только «значимые» мысли (по тегам/ключевым словам) await this.addLongTerm(t.text, 'experience'); } } }