feat: extract and save memories after chat conversations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 13:02:42 -05:00
parent 196f8c8ae5
commit e488b2b227

View File

@@ -3,6 +3,7 @@ import logging
import random import random
import re import re
from collections import deque from collections import deque
from datetime import datetime, timedelta, timezone
from pathlib import Path from pathlib import Path
import discord import discord
@@ -32,6 +33,8 @@ class ChatCog(commands.Cog):
self._chat_history: dict[int, deque] = {} self._chat_history: dict[int, deque] = {}
# Counter of messages seen since last proactive reply (per channel) # Counter of messages seen since last proactive reply (per channel)
self._messages_since_reply: dict[int, int] = {} self._messages_since_reply: dict[int, int] = {}
# Users whose profile has been updated and needs DB flush
self._dirty_users: set[int] = set()
def _get_active_prompt(self) -> str: def _get_active_prompt(self) -> str:
"""Load the chat prompt for the current mode.""" """Load the chat prompt for the current mode."""
@@ -39,6 +42,51 @@ class ChatCog(commands.Cog):
prompt_file = mode_config.get("prompt_file", "chat_personality.txt") prompt_file = mode_config.get("prompt_file", "chat_personality.txt")
return _load_prompt(prompt_file) return _load_prompt(prompt_file)
async def _extract_and_save_memories(
self, user_id: int, username: str, conversation: list[dict[str, str]],
) -> None:
"""Background task: extract memories from conversation and save them."""
try:
current_profile = self.bot.drama_tracker.get_user_notes(user_id)
result = await self.bot.llm.extract_memories(
conversation, username, current_profile,
)
if not result:
return
# Save expiring memories
for mem in result.get("memories", []):
if mem["expiration"] == "permanent":
continue # permanent facts go into profile_update
exp_days = {"1d": 1, "3d": 3, "7d": 7, "30d": 30}
days = exp_days.get(mem["expiration"], 7)
expires_at = datetime.now(timezone.utc) + timedelta(days=days)
await self.bot.db.save_memory(
user_id=user_id,
memory=mem["memory"],
topics=",".join(mem["topics"]),
importance=mem["importance"],
expires_at=expires_at,
source="chat",
)
# Prune if over cap
await self.bot.db.prune_excess_memories(user_id)
# Update profile if warranted
profile_update = result.get("profile_update")
if profile_update:
self.bot.drama_tracker.set_user_profile(user_id, profile_update)
self._dirty_users.add(user_id)
logger.info(
"Extracted %d memories for %s (profile_update=%s)",
len(result.get("memories", [])),
username,
bool(profile_update),
)
except Exception:
logger.exception("Failed to extract memories for %s", username)
@commands.Cog.listener() @commands.Cog.listener()
async def on_message(self, message: discord.Message): async def on_message(self, message: discord.Message):
if message.author.bot: if message.author.bot:
@@ -265,6 +313,14 @@ class ChatCog(commands.Cog):
await message.reply(response, mention_author=False) await message.reply(response, mention_author=False)
# Fire-and-forget memory extraction
if not image_attachment:
asyncio.create_task(self._extract_and_save_memories(
message.author.id,
message.author.display_name,
list(self._chat_history[ch_id]),
))
reply_type = "proactive" if is_proactive else "chat" reply_type = "proactive" if is_proactive else "chat"
logger.info( logger.info(
"%s reply in #%s to %s: %s", "%s reply in #%s to %s: %s",