-
${escapeHtml(mem.content)}
+
${escapeHtml(mem.content)}${personaBadge}
Source: ${escapeHtml(source)} ยท ${when}
diff --git a/cat-plugins/discord_bridge/discord_bridge.py b/cat-plugins/discord_bridge/discord_bridge.py
index d77ae8f..80bf196 100644
--- a/cat-plugins/discord_bridge/discord_bridge.py
+++ b/cat-plugins/discord_bridge/discord_bridge.py
@@ -97,8 +97,12 @@ def before_cat_stores_episodic_memory(doc, cat):
if author_name:
doc.metadata['author_name'] = author_name
+ # Tag with persona so Evil Miku and Normal Miku know whose memory this is
+ evil_mode = cat.working_memory.get('evil_mode', False)
+ doc.metadata['persona'] = 'evil_miku' if evil_mode else 'miku'
+
print(f"๐พ [Discord Bridge] Storing memory (unconsolidated): {message[:50]}...")
- print(f" User: {cat.user_id}, Guild: {guild_id}, Author: {author_name}")
+ print(f" User: {cat.user_id}, Guild: {guild_id}, Author: {author_name}, Persona: {doc.metadata['persona']}")
return doc
diff --git a/cat-plugins/memory_consolidation/memory_consolidation.py b/cat-plugins/memory_consolidation/memory_consolidation.py
index e863f53..a10ea6a 100644
--- a/cat-plugins/memory_consolidation/memory_consolidation.py
+++ b/cat-plugins/memory_consolidation/memory_consolidation.py
@@ -77,19 +77,34 @@ def agent_prompt_prefix(prefix, cat):
)
if results:
- high_confidence_facts = [
- item[0].page_content
- for item in results
- if item[1] > 0.5
- ]
+ # Build list of (fact_text, persona) tuples from results
+ high_confidence_facts = []
+ seen_personas = set()
+ for item in results:
+ if item[1] > 0.5:
+ doc = item[0]
+ fact_text = doc.page_content
+ fact_persona = doc.metadata.get('persona', 'miku')
+ high_confidence_facts.append((fact_text, fact_persona))
+ seen_personas.add(fact_persona)
if high_confidence_facts:
+ # Determine which persona is currently active
+ current_evil = cat.working_memory.get('evil_mode', False)
+ current_persona = 'evil_miku' if current_evil else 'miku'
+
+ # Build the facts section with persona annotations
facts_text = "\n\n## Personal Facts About the User:\n"
- for fact in high_confidence_facts:
- facts_text += f"- {fact}\n"
+ for fact, fact_persona in high_confidence_facts:
+ # Annotate facts that came from the OTHER persona
+ if fact_persona != current_persona:
+ source_label = "Evil Miku" if fact_persona == 'evil_miku' else "Miku"
+ facts_text += f"- {fact} (learned as {source_label})\n"
+ else:
+ facts_text += f"- {fact}\n"
facts_text += "\n(Use these facts when answering the user's question)\n"
prefix += facts_text
- print(f"[Declarative] Injected {len(high_confidence_facts)} facts into prompt")
+ print(f"[Declarative] Injected {len(high_confidence_facts)} facts into prompt (personas: {seen_personas}, current: {current_persona})")
except Exception as e:
print(f"[Declarative] Error: {e}")
@@ -142,11 +157,16 @@ def before_cat_sends_message(message, cat):
miku_response = str(message)
if miku_response and len(miku_response.strip()) > 3:
+ # Determine which persona is active so the memory is tagged correctly
+ evil_mode = cat.working_memory.get('evil_mode', False)
+ persona = 'evil_miku' if evil_mode else 'miku'
+
metadata = {
'source': cat.user_id,
'when': datetime.now().timestamp(),
'stored_at': datetime.now().isoformat(),
'speaker': 'miku',
+ 'persona': persona,
'consolidated': False,
'guild_id': cat.working_memory.get('guild_id', 'dm'),
'channel_id': cat.working_memory.get('channel_id'),
@@ -160,7 +180,7 @@ def before_cat_sends_message(message, cat):
vector=vector,
metadata=metadata
)
- print(f"[Miku Memory] Stored response: {miku_response[:50]}...")
+ print(f"[Miku Memory] Stored response ({persona}): {miku_response[:50]}...")
except Exception as e:
print(f"[Miku Memory] Error storing response: {e}")
@@ -211,7 +231,9 @@ def trigger_consolidation_sync(cat):
to_delete = []
to_mark_consolidated = []
# Group user messages by source (user_id) for per-user fact extraction
+ # Also track which persona was active for each user's messages
user_messages_by_source = {}
+ user_persona_by_source = {} # source -> set of personas seen
for point in memories:
content = point.payload.get('page_content', '').strip()
@@ -235,7 +257,11 @@ def trigger_consolidation_sync(cat):
source = metadata.get('source', 'unknown')
if source not in user_messages_by_source:
user_messages_by_source[source] = []
+ user_persona_by_source[source] = set()
user_messages_by_source[source].append(point.id)
+ # Track which persona was active when this message was stored
+ msg_persona = metadata.get('persona', 'miku')
+ user_persona_by_source[source].add(msg_persona)
# Delete trivial memories
if to_delete:
@@ -255,8 +281,11 @@ def trigger_consolidation_sync(cat):
# Extract facts per user
total_facts = 0
for source_user_id, memory_ids in user_messages_by_source.items():
- print(f"[Consolidation] Extracting facts for user '{source_user_id}' from {len(memory_ids)} messages...")
- facts = extract_and_store_facts(client, memory_ids, cat, source_user_id)
+ # Determine the dominant persona for this user's messages
+ personas = user_persona_by_source.get(source_user_id, {'miku'})
+ dominant_persona = 'evil_miku' if 'evil_miku' in personas else 'miku'
+ print(f"[Consolidation] Extracting facts for user '{source_user_id}' from {len(memory_ids)} messages (persona: {dominant_persona})...")
+ facts = extract_and_store_facts(client, memory_ids, cat, source_user_id, dominant_persona)
total_facts += facts
print(f"[Consolidation] Extracted {facts} facts for user '{source_user_id}'")
@@ -275,10 +304,10 @@ def trigger_consolidation_sync(cat):
# FACT EXTRACTION
# ===================================================================
-def extract_and_store_facts(client, memory_ids, cat, user_id):
+def extract_and_store_facts(client, memory_ids, cat, user_id, persona='miku'):
"""
Extract declarative facts from user memories using LLM and store them.
- Facts are scoped to the specific user_id.
+ Facts are scoped to the specific user_id and tagged with the source persona.
Uses Cat's embedder to ensure vector compatibility.
Deduplicates against existing facts before storing.
"""
@@ -300,8 +329,16 @@ def extract_and_store_facts(client, memory_ids, cat, user_id):
for mem in batch
])
- extraction_prompt = f"""Analyze these user messages and extract ONLY factual personal information.
+ # Add persona context to the extraction prompt so the LLM knows
+ # which version of Miku was active during these conversations
+ persona_context = ""
+ if persona == 'evil_miku':
+ persona_context = "\nNOTE: These messages were exchanged with Evil Miku (the cruel, sadistic alter-ego).\n"
+ else:
+ persona_context = "\nNOTE: These messages were exchanged with Normal Miku (the cheerful virtual idol).\n"
+ extraction_prompt = f"""Analyze these user messages and extract ONLY factual personal information.
+{persona_context}
User messages:
{conversation_context}
@@ -395,13 +432,14 @@ IMPORTANT:
'when': datetime.now().timestamp(),
'fact_type': fact_type,
'fact_value': fact_value,
+ 'persona': persona,
}
}
}]
)
facts_stored += 1
- print(f"[Fact Stored] [{user_id}] {fact_text}")
+ print(f"[Fact Stored] [{user_id}] ({persona}) {fact_text}")
except Exception as e:
print(f"[LLM Extract] Error: {e}")