Spaces:
Sleeping
Sleeping
Ayush
feat: Planner-Executor AI agent with autonomous task execution, memory, and weather tool
7d3b88b | /** | |
| * memoryManager.js β Profile (localStorage) + Session (sessionStorage) handler. | |
| * | |
| * Profile: Stable user facts (name, job, preferences). Max 15 entries. | |
| * - Confidence-based conflict resolution (no blind overwrite) | |
| * - Identity keys (name, job, role) are never deleted, only updated with higher confidence | |
| * - Dedup by key | |
| * | |
| * Session: Temporary context (references, tasks). Max 10 entries, FIFO. | |
| * - Only 'reference' and 'task' intents stored | |
| * - Only last 5 injected into requests | |
| */ | |
| const PROFILE_STORAGE_KEY = 'agent_profile_memory' | |
| const SESSION_STORAGE_KEY = 'agent_session_memory' | |
| const MAX_PROFILE_ENTRIES = 15 | |
| const MAX_SESSION_ENTRIES = 10 | |
| const MAX_SESSION_INJECTION = 5 | |
| const IDENTITY_KEYS = new Set(['name', 'job', 'role', 'profession', 'location']) | |
| const CONFIDENCE_TOLERANCE = 0.1 // within this range = "uncertain", keep both | |
| // ββ Profile Memory (localStorage) βββββββββββββββββββββββββββββββββββββ | |
| function getProfile() { | |
| try { | |
| const raw = localStorage.getItem(PROFILE_STORAGE_KEY) | |
| return raw ? JSON.parse(raw) : [] | |
| } catch { | |
| return [] | |
| } | |
| } | |
| function setProfile(entries) { | |
| try { | |
| localStorage.setItem(PROFILE_STORAGE_KEY, JSON.stringify(entries)) | |
| } catch (e) { | |
| console.warn('[MemoryManager] Failed to write profile:', e) | |
| } | |
| } | |
| /** | |
| * Save a profile entry with conflict resolution. | |
| * | |
| * Rules: | |
| * - New confidence > existing β overwrite | |
| * - New confidence β existing (within tolerance) and values differ β keep both (as array) | |
| * - New confidence < existing β discard new | |
| * - Identity keys are never deleted, only updated with higher confidence | |
| * - Max 15 entries; on overflow, evict lowest-confidence non-identity entry | |
| */ | |
| function saveProfile(extraction) { | |
| const { data, confidence, intent } = extraction | |
| const key = data?.key | |
| const value = data?.value | |
| if (!key || !value) return | |
| const entries = getProfile() | |
| const existingIndex = entries.findIndex((e) => e.key === key) | |
| if (existingIndex >= 0) { | |
| const existing = entries[existingIndex] | |
| const confDiff = confidence - existing.confidence | |
| if (confDiff > CONFIDENCE_TOLERANCE) { | |
| // New is clearly higher confidence β overwrite | |
| entries[existingIndex] = { key, value, confidence, intent, timestamp: Date.now() } | |
| } else if (confDiff >= -CONFIDENCE_TOLERANCE && existing.value !== value) { | |
| // Similar confidence but different values β keep both | |
| const combinedValue = Array.isArray(existing.value) | |
| ? [...new Set([...existing.value, value])] | |
| : existing.value === value | |
| ? existing.value | |
| : [existing.value, value] | |
| entries[existingIndex] = { | |
| key, | |
| value: combinedValue, | |
| confidence: Math.max(confidence, existing.confidence), | |
| intent, | |
| timestamp: Date.now(), | |
| } | |
| } | |
| // else: new confidence is lower β discard (do nothing) | |
| } else { | |
| // New key β add it | |
| entries.push({ key, value, confidence, intent, timestamp: Date.now() }) | |
| } | |
| // Enforce cap | |
| while (entries.length > MAX_PROFILE_ENTRIES) { | |
| // Find lowest-confidence non-identity entry to evict | |
| let evictIndex = -1 | |
| let lowestConf = Infinity | |
| for (let i = 0; i < entries.length; i++) { | |
| if (!IDENTITY_KEYS.has(entries[i].key) && entries[i].confidence < lowestConf) { | |
| lowestConf = entries[i].confidence | |
| evictIndex = i | |
| } | |
| } | |
| if (evictIndex >= 0) { | |
| entries.splice(evictIndex, 1) | |
| } else { | |
| // All entries are identity β evict oldest | |
| entries.shift() | |
| } | |
| } | |
| setProfile(entries) | |
| } | |
| // ββ Session Memory (sessionStorage) βββββββββββββββββββββββββββββββββββ | |
| function getSession() { | |
| try { | |
| const raw = sessionStorage.getItem(SESSION_STORAGE_KEY) | |
| return raw ? JSON.parse(raw) : [] | |
| } catch { | |
| return [] | |
| } | |
| } | |
| function setSession(entries) { | |
| try { | |
| sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(entries)) | |
| } catch (e) { | |
| console.warn('[MemoryManager] Failed to write session:', e) | |
| } | |
| } | |
| /** | |
| * Save a session entry. Only 'reference' and 'task' intents accepted. | |
| * FIFO eviction at max 10 entries. | |
| */ | |
| function saveSession(extraction) { | |
| const { data, confidence, intent } = extraction | |
| const key = data?.key | |
| const value = data?.value | |
| if (!key || !value) return | |
| // Only store reference and task intents | |
| if (intent !== 'reference' && intent !== 'task') return | |
| const entries = getSession() | |
| // Dedup by key β update if exists | |
| const existingIndex = entries.findIndex((e) => e.key === key) | |
| if (existingIndex >= 0) { | |
| entries[existingIndex] = { key, value, confidence, intent, timestamp: Date.now() } | |
| } else { | |
| entries.push({ key, value, confidence, intent, timestamp: Date.now() }) | |
| } | |
| // FIFO eviction | |
| while (entries.length > MAX_SESSION_ENTRIES) { | |
| entries.shift() | |
| } | |
| setSession(entries) | |
| } | |
| // ββ Injection helpers βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| /** | |
| * Get formatted profile entries for backend injection. | |
| * Returns all profile entries. | |
| */ | |
| function getProfileForInjection() { | |
| return getProfile() | |
| } | |
| /** | |
| * Get formatted session entries for backend injection. | |
| * Returns only the last 5 entries (most recent). | |
| */ | |
| function getSessionForInjection() { | |
| const entries = getSession() | |
| return entries.slice(-MAX_SESSION_INJECTION) | |
| } | |
| // ββ Main handler ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| /** | |
| * Process a memory extraction result from the backend. | |
| * Routes to profile or session storage based on memory_type. | |
| */ | |
| function processExtraction(extraction) { | |
| if (!extraction || !extraction.store) return | |
| if (extraction.memory_type === 'profile') { | |
| saveProfile(extraction) | |
| } else if (extraction.memory_type === 'session') { | |
| saveSession(extraction) | |
| } | |
| } | |
| export const MemoryManager = { | |
| getProfileForInjection, | |
| getSessionForInjection, | |
| processExtraction, | |
| // Exposed for debugging / insights panel | |
| getProfile, | |
| getSession, | |
| } | |
| export default MemoryManager | |