feat: Phase 1 - Discord bridge with unified user identity
Implements unified cross-server memory system for Miku bot:
**Core Changes:**
- discord_bridge plugin with 3 hooks for metadata enrichment
- Unified user identity: discord_user_{id} across servers and DMs
- Minimal filtering: skip only trivial messages (lol, k, 1-2 chars)
- Marks all memories as consolidated=False for Phase 2 processing
**Testing:**
- test_phase1.py validates cross-server memory recall
- PHASE1_TEST_RESULTS.md documents successful validation
- Cross-server test: User says 'blue' in Server A, Miku remembers in Server B ✅
**Documentation:**
- IMPLEMENTATION_PLAN.md - Complete architecture and roadmap
- Phase 2 (sleep consolidation) ready for implementation
This lays the foundation for human-like memory consolidation.
This commit is contained in:
Binary file not shown.
99
cheshire-cat/cat/plugins/discord_bridge/discord_bridge.py
Normal file
99
cheshire-cat/cat/plugins/discord_bridge/discord_bridge.py
Normal file
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
Discord Bridge Plugin for Cheshire Cat
|
||||
|
||||
This plugin enriches Cat's memory system with Discord context:
|
||||
- Unified user identity across all servers and DMs
|
||||
- Guild/channel metadata for context tracking
|
||||
- Minimal filtering before storage (only skip obvious junk)
|
||||
- Marks memories as unconsolidated for nightly processing
|
||||
|
||||
Phase 1 Implementation
|
||||
"""
|
||||
|
||||
from cat.mad_hatter.decorators import hook
|
||||
from datetime import datetime
|
||||
import re
|
||||
|
||||
|
||||
@hook(priority=100)
|
||||
def before_cat_reads_message(user_message_json: dict, cat) -> dict:
|
||||
"""
|
||||
Enrich incoming message with Discord metadata.
|
||||
This runs BEFORE the message is processed.
|
||||
"""
|
||||
# Extract Discord context from working memory or metadata
|
||||
# These will be set by the Discord bot when calling the Cat API
|
||||
guild_id = cat.working_memory.get('guild_id')
|
||||
channel_id = cat.working_memory.get('channel_id')
|
||||
|
||||
# Add to message metadata for later use
|
||||
if 'metadata' not in user_message_json:
|
||||
user_message_json['metadata'] = {}
|
||||
|
||||
user_message_json['metadata']['guild_id'] = guild_id or 'dm'
|
||||
user_message_json['metadata']['channel_id'] = channel_id
|
||||
user_message_json['metadata']['timestamp'] = datetime.now().isoformat()
|
||||
|
||||
return user_message_json
|
||||
|
||||
|
||||
@hook(priority=100)
|
||||
def before_cat_stores_episodic_memory(doc, cat):
|
||||
"""
|
||||
Filter and enrich memories before storage.
|
||||
|
||||
Phase 1: Minimal filtering
|
||||
- Skip only obvious junk (1-2 char messages, pure reactions)
|
||||
- Store everything else temporarily
|
||||
- Mark as unconsolidated for nightly processing
|
||||
"""
|
||||
message = doc.page_content.strip()
|
||||
|
||||
# Skip only the most trivial messages
|
||||
skip_patterns = [
|
||||
r'^\w{1,2}$', # 1-2 character messages: "k", "ok"
|
||||
r'^(lol|lmao|haha|hehe|xd|rofl)$', # Pure reactions
|
||||
r'^:[\w_]+:$', # Discord emoji only: ":smile:"
|
||||
]
|
||||
|
||||
for pattern in skip_patterns:
|
||||
if re.match(pattern, message.lower()):
|
||||
print(f"🗑️ [Discord Bridge] Skipping trivial message: {message}")
|
||||
return None # Don't store at all
|
||||
|
||||
# Add Discord metadata to memory
|
||||
doc.metadata['consolidated'] = False # Needs nightly processing
|
||||
doc.metadata['stored_at'] = datetime.now().isoformat()
|
||||
|
||||
# Get Discord context from working memory
|
||||
guild_id = cat.working_memory.get('guild_id')
|
||||
channel_id = cat.working_memory.get('channel_id')
|
||||
|
||||
doc.metadata['guild_id'] = guild_id or 'dm'
|
||||
doc.metadata['channel_id'] = channel_id
|
||||
doc.metadata['source'] = 'discord'
|
||||
|
||||
print(f"💾 [Discord Bridge] Storing memory (unconsolidated): {message[:50]}...")
|
||||
print(f" User: {cat.user_id}, Guild: {doc.metadata['guild_id']}, Channel: {channel_id}")
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
@hook(priority=50)
|
||||
def after_cat_recalls_memories(memory_docs, cat):
|
||||
"""
|
||||
Log memory recall for debugging.
|
||||
Can be used to filter by guild_id if needed in the future.
|
||||
"""
|
||||
if memory_docs:
|
||||
print(f"🧠 [Discord Bridge] Recalled {len(memory_docs)} memories for user {cat.user_id}")
|
||||
# Show which guilds the memories are from
|
||||
guilds = set(doc.metadata.get('guild_id', 'unknown') for doc in memory_docs)
|
||||
print(f" From guilds: {', '.join(guilds)}")
|
||||
|
||||
return memory_docs
|
||||
|
||||
|
||||
# Plugin metadata
|
||||
__version__ = "1.0.0"
|
||||
__description__ = "Discord bridge with unified user identity and sleep consolidation support"
|
||||
10
cheshire-cat/cat/plugins/discord_bridge/plugin.json
Normal file
10
cheshire-cat/cat/plugins/discord_bridge/plugin.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Discord Bridge",
|
||||
"description": "Discord integration with unified user identity and sleep consolidation support",
|
||||
"author_name": "Miku Bot Team",
|
||||
"author_url": "",
|
||||
"plugin_url": "",
|
||||
"tags": "discord, memory, consolidation",
|
||||
"thumb": "",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
1
cheshire-cat/cat/plugins/discord_bridge/settings.json
Normal file
1
cheshire-cat/cat/plugins/discord_bridge/settings.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
Reference in New Issue
Block a user