clawdbot-workspace/memory_interface.py
2026-01-28 23:00:58 -05:00

283 lines
7.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Memory interface for Clawdbot runtime integration.
This module provides the bridge between Clawdbot (Node.js) and the
Python memory system. It can be used in three ways:
1. CLI mode: Called as subprocess from Node.js
python memory_interface.py search "query" --guild 123 --limit 5
python memory_interface.py context "message text" --guild 123 --user 456
2. JSON-RPC mode: Run as a simple server
python memory_interface.py serve --port 9876
3. Direct import: From other Python code
from memory_interface import get_context_for_message, remember
"""
import sys
import os
import json
import argparse
from typing import Optional, List, Dict, Any
# Add workspace to path for imports
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# Import from memory-retrieval.py (hyphenated filename)
import importlib.util
spec = importlib.util.spec_from_file_location(
"memory_retrieval",
os.path.join(os.path.dirname(os.path.abspath(__file__)), "memory-retrieval.py")
)
memory_retrieval = importlib.util.module_from_spec(spec)
spec.loader.exec_module(memory_retrieval)
search_memories = memory_retrieval.search_memories
add_memory = memory_retrieval.add_memory
get_recent_memories = memory_retrieval.get_recent_memories
supersede_memory = memory_retrieval.supersede_memory
get_memory_stats = memory_retrieval.get_memory_stats
ensure_secure_permissions = memory_retrieval.ensure_secure_permissions
def get_context_for_message(
message: str,
guild_id: Optional[str] = None,
channel_id: Optional[str] = None,
user_id: Optional[str] = None,
limit: int = 5
) -> str:
"""
Get relevant memory context for responding to a message.
Call this before generating a response to inject memory.
Args:
message: The incoming message text
guild_id: Discord guild ID (for scoping)
channel_id: Discord channel ID
user_id: Discord user ID
limit: Maximum number of memories to return
Returns:
Formatted string of relevant memories for prompt injection
"""
# Security check
ensure_secure_permissions(warn=False)
# Search for relevant memories
results = search_memories(
query=message,
guild_id=guild_id,
user_id=user_id,
limit=limit
)
if not results:
# Fall back to recent memories for this guild
results = get_recent_memories(guild_id=guild_id, limit=3)
if not results:
return ""
# Format for context injection
lines = ["[Relevant Memories]"]
for r in results:
mem_type = r.get('memory_type', 'fact')
content = r.get('content', '')
# Truncate long content
if len(content) > 200:
content = content[:200] + "..."
lines.append(f"- [{mem_type}] {content}")
return "\n".join(lines)
def should_remember(response_text: str) -> bool:
"""
Check if the bot's response indicates something should be remembered.
"""
triggers = [
"i'll remember",
"i've noted",
"got it",
"noted",
"understood",
"i'll keep that in mind",
"remembered",
]
lower = response_text.lower()
return any(t in lower for t in triggers)
def remember(
content: str,
memory_type: str = "fact",
guild_id: Optional[str] = None,
channel_id: Optional[str] = None,
user_id: Optional[str] = None,
source: str = "conversation"
) -> Dict[str, Any]:
"""
Store a new memory.
Args:
content: What to remember
memory_type: Type (fact, preference, event, relationship, project, person, security)
guild_id: Discord guild ID
channel_id: Discord channel ID
user_id: Discord user ID who provided the info
source: Where this memory came from
Returns:
Dict with memory_id and status
"""
ensure_secure_permissions(warn=False)
try:
memory_id = add_memory(
content=content,
memory_type=memory_type,
guild_id=guild_id,
channel_id=channel_id,
user_id=user_id,
source=source
)
return {"success": True, "memory_id": memory_id}
except Exception as e:
return {"success": False, "error": str(e)}
def update_memory(
old_id: int,
new_content: str,
reason: str = "updated"
) -> Dict[str, Any]:
"""
Update an existing memory by superseding it.
Args:
old_id: ID of the memory to update
new_content: New content
reason: Why it's being updated
Returns:
Dict with new memory_id and status
"""
ensure_secure_permissions(warn=False)
try:
new_id = supersede_memory(old_id, new_content, reason)
return {"success": True, "old_id": old_id, "new_id": new_id}
except Exception as e:
return {"success": False, "error": str(e)}
def search(
query: str,
guild_id: Optional[str] = None,
memory_type: Optional[str] = None,
limit: int = 10
) -> List[Dict[str, Any]]:
"""
Search memories.
Args:
query: Search query
guild_id: Filter to guild
memory_type: Filter to type
limit: Max results
Returns:
List of matching memories
"""
ensure_secure_permissions(warn=False)
return search_memories(
query=query,
guild_id=guild_id,
memory_type=memory_type,
limit=limit
)
def stats() -> Dict[str, Any]:
"""Get memory statistics."""
ensure_secure_permissions(warn=False)
return get_memory_stats()
# CLI Interface
def main():
parser = argparse.ArgumentParser(description="Clawdbot Memory Interface")
subparsers = parser.add_subparsers(dest="command", help="Command")
# Search command
search_p = subparsers.add_parser("search", help="Search memories")
search_p.add_argument("query", help="Search query")
search_p.add_argument("--guild", help="Guild ID filter")
search_p.add_argument("--type", help="Memory type filter")
search_p.add_argument("--limit", type=int, default=10, help="Max results")
# Context command (for message processing)
ctx_p = subparsers.add_parser("context", help="Get context for message")
ctx_p.add_argument("message", help="Message text")
ctx_p.add_argument("--guild", help="Guild ID")
ctx_p.add_argument("--channel", help="Channel ID")
ctx_p.add_argument("--user", help="User ID")
ctx_p.add_argument("--limit", type=int, default=5, help="Max memories")
# Remember command
rem_p = subparsers.add_parser("remember", help="Store a memory")
rem_p.add_argument("content", help="Content to remember")
rem_p.add_argument("--type", default="fact", help="Memory type")
rem_p.add_argument("--guild", help="Guild ID")
rem_p.add_argument("--channel", help="Channel ID")
rem_p.add_argument("--user", help="User ID")
# Stats command
subparsers.add_parser("stats", help="Get memory statistics")
args = parser.parse_args()
if args.command == "search":
results = search(
query=args.query,
guild_id=args.guild,
memory_type=getattr(args, 'type', None),
limit=args.limit
)
print(json.dumps(results, indent=2))
elif args.command == "context":
context = get_context_for_message(
message=args.message,
guild_id=args.guild,
channel_id=args.channel,
user_id=args.user,
limit=args.limit
)
print(context)
elif args.command == "remember":
result = remember(
content=args.content,
memory_type=getattr(args, 'type', 'fact'),
guild_id=args.guild,
channel_id=args.channel,
user_id=args.user
)
print(json.dumps(result))
elif args.command == "stats":
print(json.dumps(stats(), indent=2))
else:
parser.print_help()
sys.exit(1)
if __name__ == "__main__":
main()