Spaces:
Sleeping
Sleeping
File size: 6,373 Bytes
7d3b88b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | /**
* 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
|