feat(memory): complete M.2-M.5 memory upgrade phases with tests

- M.2: ForgettingCurve, MemoryDecay, MemoryReinforcement (selective forgetting)
- M.3: DailyDigestGenerator, ReminderScheduler, ProactiveInformer (proactive reminders)
- M.4: MemoryExtractor with LLM-based memory extraction from conversations
- M.5: MemoryRecallInjector with token budget control for prompt injection
- All phases include comprehensive unit tests (109 tests passing)
- Updated checklist.md to mark all tasks complete
This commit is contained in:
2026-04-05 14:09:51 +08:00
parent 9bfa0dcc11
commit 11160ec4d2
22 changed files with 4117 additions and 186 deletions

View File

@@ -0,0 +1,81 @@
"""
MemoryDecay
Handles memory archiving, deprioritization, and restoration.
"""
from datetime import UTC, datetime
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from app.models.memory import UserMemory
class MemoryDecay:
"""Handle memory archiving and deprioritization decisions."""
ARCHIVE_THRESHOLD = 0.2
DEPRIORITIZE_THRESHOLD = 0.5
def evaluate(self, memory: "UserMemory") -> dict:
"""Evaluate memory and return action recommendation.
Returns:
dict with keys: decay_score, should_archive, should_deprioritize, action
"""
from app.services.memory.forgetting_curve import ForgettingCurve
curve = ForgettingCurve()
decay_score = curve.calculate_decay(memory)
archive = decay_score < self.ARCHIVE_THRESHOLD
deprioritize = decay_score < self.DEPRIORITIZE_THRESHOLD
if archive:
action = "archive"
elif deprioritize:
action = "deprioritize"
else:
action = "keep_active"
return {
"decay_score": decay_score,
"should_archive": archive,
"should_deprioritize": deprioritize,
"action": action,
}
def archive_memory(self, memory: "UserMemory") -> "UserMemory":
"""Archive a memory (set is_archived=True, reset decay_score to low value).
Archived memories are moved to cold storage and not included in
active reminders or context injection.
"""
memory.is_archived = True
memory.decay_score = 0.1 # Very low, will be restored on access
memory.archive_at = datetime.now(UTC)
return memory
def deprioritize_memory(self, memory: "UserMemory") -> "UserMemory":
"""Mark a memory as deprioritized (excluded from active reminders).
Unlike archival, the memory is still accessible and included in
context injection if relevant.
"""
# Just update decay_score, the importance_level already encodes priority
from app.services.memory.forgetting_curve import ForgettingCurve
curve = ForgettingCurve()
memory.decay_score = curve.calculate_decay(memory)
return memory
def restore_from_archive(self, memory: "UserMemory") -> "UserMemory":
"""Restore a memory from archive.
Resets is_archived=False and decay_score=0.8 (strong retention).
The memory is moved back to hot storage.
"""
memory.is_archived = False
memory.decay_score = 0.8 # Strong retention after restore
memory.last_accessed_at = datetime.now(UTC)
memory.archive_at = None
return memory