Files
loyal_companion/docs/architecture.md
latte dbd534d860 refactor: Transform daemon_boyfriend into Loyal Companion
Rebrand and personalize the bot as 'Bartender' - a companion for those
who love deeply and feel intensely.

Major changes:
- Rename package: daemon_boyfriend -> loyal_companion
- New default personality: Bartender - wise, steady, non-judgmental
- Grief-aware system prompt (no toxic positivity, attachment-informed)
- New relationship levels: New Face -> Close Friend progression
- Bartender-style mood modifiers (steady presence)
- New fact types: attachment_pattern, grief_context, coping_mechanism
- Lower mood decay (0.05) for emotional stability
- Higher fact extraction rate (0.4) - Bartender pays attention

Updated all imports, configs, Docker files, and documentation.
2026-01-14 18:08:35 +01:00

22 KiB

Architecture Overview

This document provides a comprehensive overview of the Daemon Boyfriend Discord bot architecture.

Table of Contents


High-Level Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                              Discord API                                     │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                              Discord Bot (bot.py)                            │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐                          │
│  │  AIChatCog  │  │  MemoryCog  │  │  StatusCog  │                          │
│  └─────────────┘  └─────────────┘  └─────────────┘                          │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                    ┌───────────────┼───────────────┐
                    ▼               ▼               ▼
┌───────────────────────┐ ┌─────────────────┐ ┌─────────────────────────────┐
│    Core Services      │ │  Living AI      │ │      AI Providers           │
│  ┌─────────────────┐  │ │  Services       │ │  ┌─────────────────────┐    │
│  │  UserService    │  │ │  ┌───────────┐  │ │  │  OpenAI Provider    │    │
│  │  DatabaseService│  │ │  │ MoodSvc   │  │ │  │  OpenRouter Provider│    │
│  │  ConversationMgr│  │ │  │ RelSvc    │  │ │  │  Anthropic Provider │    │
│  │  SearXNGService │  │ │  │ FactSvc   │  │ │  │  Gemini Provider    │    │
│  └─────────────────┘  │ │  │ OpinionSvc│  │ │  └─────────────────────┘    │
└───────────────────────┘ │  │ StyleSvc  │  │ └─────────────────────────────┘
                          │  │ ProactSvc │  │
                          │  │ AssocSvc  │  │
                          │  │ SelfAware │  │
                          │  └───────────┘  │
                          └─────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                         PostgreSQL Database                                  │
│  ┌──────────────────────────────┐  ┌──────────────────────────────────────┐ │
│  │ Core Tables                  │  │ Living AI Tables                     │ │
│  │ users, guilds, conversations │  │ bot_states, user_relationships,     │ │
│  │ messages, user_facts         │  │ mood_history, scheduled_events      │ │
│  └──────────────────────────────┘  └──────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘

Directory Structure

loyal-companion/
├── src/loyal_companion/
│   ├── __main__.py          # Entry point
│   ├── bot.py               # Discord bot setup & cog loading
│   ├── config.py            # Pydantic settings configuration
│   │
│   ├── cogs/                # Discord command handlers
│   │   ├── ai_chat.py       # @mention response handler
│   │   ├── memory.py        # Memory management commands
│   │   └── status.py        # Bot status & health commands
│   │
│   ├── models/              # SQLAlchemy ORM models
│   │   ├── base.py          # Base model with PortableJSON
│   │   ├── user.py          # User, UserFact, UserPreference
│   │   ├── conversation.py  # Conversation, Message
│   │   ├── guild.py         # Guild, GuildMember
│   │   └── living_ai.py     # All Living AI models
│   │
│   └── services/            # Business logic layer
│       ├── ai_service.py    # AI provider factory
│       ├── database.py      # Database connection management
│       ├── user_service.py  # User CRUD operations
│       ├── conversation.py  # In-memory conversation manager
│       ├── persistent_conversation.py  # DB-backed conversations
│       ├── searxng.py       # Web search integration
│       ├── monitoring.py    # Health & metrics tracking
│       │
│       ├── providers/       # AI provider implementations
│       │   ├── base.py      # Abstract AIProvider
│       │   ├── openai.py
│       │   ├── openrouter.py
│       │   ├── anthropic.py
│       │   └── gemini.py
│       │
│       └── [Living AI Services]
│           ├── mood_service.py
│           ├── relationship_service.py
│           ├── fact_extraction_service.py
│           ├── opinion_service.py
│           ├── communication_style_service.py
│           ├── proactive_service.py
│           ├── association_service.py
│           └── self_awareness_service.py
│
├── alembic/                 # Database migrations
├── tests/                   # Test suite
├── schema.sql               # Database schema definition
├── docker-compose.yml       # Docker deployment
└── requirements.txt         # Python dependencies

Core Components

1. Discord Bot Layer

The bot uses discord.py with a cog-based architecture.

Entry Point (__main__.py)

  • Creates the bot instance
  • Initializes database connection
  • Starts the Discord event loop

Bot Setup (bot.py)

  • Configures Discord intents
  • Auto-loads all cogs from cogs/ directory
  • Handles bot lifecycle events

Cogs

Cog Purpose Trigger
AIChatCog Main conversation handler @mention
MemoryCog User memory management !setname, !remember, etc.
StatusCog Health & metrics !status (admin)

2. Service Layer

Services contain all business logic and are designed to be:

  • Stateless: No instance state, all state in database
  • Async-first: All I/O operations are async
  • Testable: Accept database sessions as parameters

Core Services

Service Responsibility
AIService Factory for AI providers, prompt enhancement
DatabaseService Connection pool, session management
UserService User CRUD, facts, preferences
ConversationManager In-memory conversation history
PersistentConversationManager Database-backed conversations
SearXNGService Web search for current information
MonitoringService Health checks, metrics, error tracking

Living AI Services (see Living AI documentation)

Service Responsibility
MoodService Emotional state management
RelationshipService User relationship tracking
FactExtractionService Autonomous fact learning
OpinionService Topic sentiment tracking
CommunicationStyleService User style preferences
ProactiveService Scheduled events & follow-ups
AssociationService Cross-user memory linking
SelfAwarenessService Bot statistics & reflection

3. AI Provider Layer

Uses the Provider Pattern to support multiple AI backends.

┌────────────────────────────────────────────────────────────┐
│                      AIService                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ get_provider() → returns appropriate AIProvider     │   │
│  │ chat() → builds context and calls provider.generate │   │
│  └─────────────────────────────────────────────────────┘   │
└────────────────────────────────────────────────────────────┘
                            │
          ┌─────────────────┼─────────────────┐
          ▼                 ▼                 ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│  OpenAI Provider │ │ Anthropic Provider│ │  Gemini Provider │
│  (+ OpenRouter)  │ │                   │ │                  │
└──────────────────┘ └──────────────────┘ └──────────────────┘

4. Data Layer

Models (models/)

  • SQLAlchemy ORM with async support
  • PortableJSON type for PostgreSQL/SQLite compatibility
  • Timezone-aware UTC timestamps

Database (services/database.py)

  • Async PostgreSQL via asyncpg
  • Connection pooling
  • Graceful fallback to in-memory when no database

Data Flow

Message Processing Flow

1. User sends @mention in Discord
              │
              ▼
2. AIChatCog.on_message() receives event
              │
              ▼
3. Get or create User via UserService
              │
              ▼
4. Get or create Conversation
   (PersistentConversationManager or ConversationManager)
              │
              ▼
5. Build conversation history from messages
              │
              ▼
6. [Optional] Check if web search needed
   └─► SearXNGService.search() → add results to context
              │
              ▼
7. Apply Living AI enhancements:
   ├─► MoodService.get_current_mood()
   ├─► RelationshipService.get_relationship()
   ├─► CommunicationStyleService.get_style()
   └─► OpinionService.get_relevant_opinions()
              │
              ▼
8. Build enhanced system prompt with all context
              │
              ▼
9. AIService.chat() → Provider.generate()
              │
              ▼
10. Post-response processing:
    ├─► MoodService.update_mood()
    ├─► RelationshipService.record_interaction()
    ├─► CommunicationStyleService.record_message()
    ├─► FactExtractionService.maybe_extract_facts()
    └─► ProactiveService.check_for_events()
              │
              ▼
11. Split response if > 2000 chars
              │
              ▼
12. Send response(s) to Discord

Database Fallback Flow

┌─────────────────────────────────────────────────┐
│              Application Start                   │
└─────────────────────────────────────────────────┘
                      │
                      ▼
         ┌────────────────────────┐
         │  DATABASE_URL set?     │
         └────────────────────────┘
                │           │
              Yes          No
                │           │
                ▼           ▼
┌───────────────────┐ ┌───────────────────────┐
│ PostgreSQL Mode   │ │ In-Memory Mode        │
│ - Full persistence│ │ - ConversationManager │
│ - Living AI state │ │ - No persistence      │
│ - User facts      │ │ - Basic functionality │
└───────────────────┘ └───────────────────────┘

Design Patterns

1. Provider Pattern (AI Services)

Abstract base class with multiple implementations.

# base.py
class AIProvider(ABC):
    @abstractmethod
    async def generate(
        self,
        messages: list[Message],
        system_prompt: str | None = None,
        max_tokens: int = 1024,
        temperature: float = 0.7,
    ) -> AIResponse:
        pass

# openai.py
class OpenAIProvider(AIProvider):
    async def generate(...) -> AIResponse:
        # OpenAI-specific implementation

2. Factory Pattern (AIService)

Creates the appropriate provider based on configuration.

class AIService:
    def __init__(self):
        self.provider = self._create_provider()
    
    def _create_provider(self) -> AIProvider:
        match settings.ai_provider:
            case "openai": return OpenAIProvider()
            case "anthropic": return AnthropicProvider()
            # ...

3. Repository Pattern (Services)

Services encapsulate data access logic.

class UserService:
    @staticmethod
    async def get_user(session: AsyncSession, discord_id: int) -> User | None:
        result = await session.execute(
            select(User).where(User.discord_id == discord_id)
        )
        return result.scalar_one_or_none()

4. Cog Pattern (Discord.py)

Modular command grouping.

class MemoryCog(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
    
    @commands.command(name="setname")
    async def set_name(self, ctx, *, name: str):
        # Command implementation

5. Graceful Degradation

System works with reduced functionality when components unavailable.

# Database fallback
if settings.database_url:
    conversation_manager = PersistentConversationManager()
else:
    conversation_manager = ConversationManager()  # In-memory

# Feature toggles
if settings.living_ai_enabled and settings.mood_enabled:
    mood = await MoodService.get_current_mood(session, guild_id)
else:
    mood = None

Component Interaction Diagram

                    ┌──────────────────────────────────────────┐
                    │                User Message               │
                    └──────────────────────────────────────────┘
                                        │
                                        ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│                               AIChatCog                                       │
│                                                                               │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │ UserService │  │ Conversation│  │  SearXNG    │  │   AIService         │  │
│  │             │  │  Manager    │  │  Service    │  │                     │  │
│  │ get_user()  │  │ get_history │  │ search()    │  │ chat() ──────────┐  │  │
│  │ get_context │  │ add_message │  │             │  │                  │  │  │
│  └─────────────┘  └─────────────┘  └─────────────┘  └──────────────────│──┘  │
│                                                                        │      │
│  ┌────────────────────────────────────────────────────────────────────┼────┐ │
│  │                        Living AI Services                          │    │ │
│  │                                                                     │    │ │
│  │  ┌───────────┐ ┌──────────────┐ ┌───────────────┐ ┌────────────┐   │    │ │
│  │  │ MoodSvc   │ │ Relationship │ │ CommStyleSvc  │ │ OpinionSvc │   │    │ │
│  │  │           │ │    Svc       │ │               │ │            │   │    │ │
│  │  │get_mood() │ │get_relation()│ │ get_style()   │ │get_opinions│   │    │ │
│  │  │update()   │ │record()      │ │ record()      │ │ update()   │   │    │ │
│  │  └───────────┘ └──────────────┘ └───────────────┘ └────────────┘   │    │ │
│  │                                                                     │    │ │
│  │  ┌───────────────┐ ┌────────────────┐ ┌───────────────────────┐    │    │ │
│  │  │ FactExtract   │ │ ProactiveSvc   │ │ SelfAwarenessSvc      │    │    │ │
│  │  │               │ │                │ │                       │    │    │ │
│  │  │ extract()     │ │ detect_events()│ │ get_stats()           │    │    │ │
│  │  └───────────────┘ └────────────────┘ └───────────────────────┘    │    │ │
│  └────────────────────────────────────────────────────────────────────│────┘ │
│                                                                        │      │
└────────────────────────────────────────────────────────────────────────│──────┘
                                                                         │
                                                                         ▼
                              ┌──────────────────────────────────────────────┐
                              │               AI Provider                     │
                              │  (OpenAI / Anthropic / Gemini / OpenRouter)  │
                              └──────────────────────────────────────────────┘
                                                   │
                                                   ▼
                              ┌──────────────────────────────────────────────┐
                              │                  Response                     │
                              └──────────────────────────────────────────────┘

Next Steps