# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Build & Run Commands ```bash # Install dependencies pip install -r requirements.txt # Install in development mode (required for testing) pip install -e . # Run the bot (requires .env with DISCORD_TOKEN and AI provider key) python -m loyal_companion # Run with Docker (includes PostgreSQL) docker-compose up -d # Syntax check all Python files python -m py_compile src/loyal_companion/**/*.py ``` ## Testing ```bash # Install dev dependencies pip install -e ".[dev]" # Run all tests python -m pytest tests/ -v # Run tests with coverage python -m pytest tests/ --cov=loyal_companion --cov-report=term-missing # Run specific test file python -m pytest tests/test_models.py -v # Run specific test class python -m pytest tests/test_services.py::TestMoodService -v ``` The test suite uses: - `pytest` with `pytest-asyncio` for async test support - SQLite in-memory database for testing (via `aiosqlite`) - Mock fixtures for Discord objects and AI providers in `tests/conftest.py` ## Architecture Loyal Companion is a Discord bot companion for those who love deeply and feel intensely. It features a "Living AI" system called Bartender - a wise, steady presence who listens without judgment, understands attachment theory, and knows when to offer perspective versus when to just hold space. ### Provider Pattern The AI system uses a provider abstraction pattern: - `services/providers/base.py` defines `AIProvider` abstract class with `generate()` method - `services/providers/openai.py`, `openrouter.py`, `anthropic.py`, `gemini.py` implement the interface - `services/ai_service.py` is the factory that creates the correct provider based on `AI_PROVIDER` env var - OpenRouter uses OpenAI's client with a different base URL - Gemini uses the `google-genai` SDK ### Cog System Discord functionality is in `cogs/`: - `ai_chat.py` - `@mention` handler (responds when bot is mentioned) - `memory.py` - Memory management commands (`!setname`, `!remember`, etc.) - `status.py` - Bot health and status commands Cogs are auto-loaded by `bot.py` from the `cogs/` directory. ### Database & Memory System The bot uses PostgreSQL for persistent memory (optional, falls back to in-memory): - `models/` - SQLAlchemy models (User, UserFact, Conversation, Message, Guild, GuildMember) - `models/living_ai.py` - Living AI models (BotState, BotOpinion, UserRelationship, etc.) - `services/database.py` - Connection pool and async session management - `services/user_service.py` - User CRUD, custom names, facts management - `services/persistent_conversation.py` - Database-backed conversation history Key features: - Custom names: Set preferred names for users so the bot knows "who is who" - User facts: Bot remembers things about users (hobbies, preferences, attachment patterns, grief context) - Persistent conversations: Chat history survives restarts - Conversation timeout: New conversation starts after 60 minutes of inactivity ### Living AI System The bot implements a "Living AI" system with emotional depth and relationship tracking: #### Services (`services/`) - `mood_service.py` - Valence-arousal mood model with time decay - `relationship_service.py` - Relationship scoring (new face to close friend) - `fact_extraction_service.py` - Autonomous fact learning from conversations (including attachment patterns, grief context, coping mechanisms) - `opinion_service.py` - Bot develops opinions on topics over time - `self_awareness_service.py` - Bot statistics and self-reflection - `communication_style_service.py` - Learns user communication preferences - `proactive_service.py` - Scheduled events (birthdays, follow-ups) - `association_service.py` - Cross-user memory associations #### Models (`models/living_ai.py`) - `BotState` - Global mood state and statistics per guild - `BotOpinion` - Topic sentiments and preferences - `UserRelationship` - Per-user relationship scores and metrics - `UserCommunicationStyle` - Learned communication preferences - `ScheduledEvent` - Birthdays, follow-ups, reminders - `FactAssociation` - Cross-user memory links - `MoodHistory` - Mood changes over time #### Mood System Uses a valence-arousal model: - Valence: -1 (sad) to +1 (happy) - Arousal: -1 (calm) to +1 (excited) - Labels: excited, happy, calm, neutral, bored, annoyed, curious - Time decay: Mood gradually returns to neutral (slower decay = steadier presence) #### Relationship Levels - New Face (0-20): Warm but observant - "Pull up a seat" energy - Getting to Know You (21-40): Building trust, remembering details - Regular (41-60): Comfortable familiarity - "Your usual?" - Good Friend (61-80): Real trust, can be honest even when hard - Close Friend (81-100): Deep bond, full honesty, reflects patterns with love ### Configuration All config flows through `config.py` using pydantic-settings. The `settings` singleton is created at module load, so env vars must be set before importing. ### Web Search The bot can search the web for current information via SearXNG: - `services/searxng.py` provides `SearXNGService` for web queries - `ai_chat.py` uses a two-step approach: first asks AI if search is needed, then provides results as context - Search is triggered automatically when the AI determines the query needs current information - Configured via `SEARXNG_URL`, `SEARXNG_ENABLED`, and `SEARXNG_MAX_RESULTS` env vars ### Key Design Decisions - `PersistentConversationManager` stores conversations in PostgreSQL when `DATABASE_URL` is set - `ConversationManager` is the in-memory fallback when database is not configured - Long AI responses are split via `split_message()` in `ai_chat.py` to respect Discord's 2000 char limit - The bot responds only to @mentions via `on_message` listener - Web search uses AI to decide when to search, avoiding unnecessary API calls for general knowledge questions - User context (custom name + known facts) is included in AI prompts for personalized responses - `PortableJSON` type in `models/base.py` allows models to work with both PostgreSQL (JSONB) and SQLite (JSON) - `ensure_utc()` helper handles timezone-naive datetimes from SQLite ## Environment Variables Required: `DISCORD_TOKEN`, plus one of `OPENAI_API_KEY`, `OPENROUTER_API_KEY`, `ANTHROPIC_API_KEY`, or `GEMINI_API_KEY` depending on `AI_PROVIDER` setting. Optional: - `DATABASE_URL` - PostgreSQL connection string (e.g., `postgresql+asyncpg://user:pass@host:5432/db`) - `POSTGRES_PASSWORD` - Used by docker-compose for the PostgreSQL container - `SEARXNG_URL` - SearXNG instance URL for web search capability ### Living AI Configuration - `LIVING_AI_ENABLED` - Master switch for Living AI features (default: true) - `MOOD_ENABLED` - Enable mood system (default: true) - `RELATIONSHIP_ENABLED` - Enable relationship tracking (default: true) - `FACT_EXTRACTION_ENABLED` - Enable autonomous fact extraction (default: true) - `FACT_EXTRACTION_RATE` - Probability of extracting facts (default: 0.4) - `PROACTIVE_ENABLED` - Enable proactive messages (default: true) - `CROSS_USER_ENABLED` - Enable cross-user memory associations (default: false) - `OPINION_FORMATION_ENABLED` - Enable bot opinion formation (default: true) - `STYLE_LEARNING_ENABLED` - Enable communication style learning (default: true) - `MOOD_DECAY_RATE` - How fast mood returns to neutral per hour (default: 0.05) ### Command Toggles - `COMMANDS_ENABLED` - Master switch for all commands (default: true) - `CMD_RELATIONSHIP_ENABLED` - Enable `!relationship` command - `CMD_MOOD_ENABLED` - Enable `!mood` command - `CMD_BOTSTATS_ENABLED` - Enable `!botstats` command - `CMD_OURHISTORY_ENABLED` - Enable `!ourhistory` command - `CMD_BIRTHDAY_ENABLED` - Enable `!birthday` command - `CMD_REMEMBER_ENABLED` - Enable `!remember` command - `CMD_SETNAME_ENABLED` - Enable `!setname` command - `CMD_WHATDOYOUKNOW_ENABLED` - Enable `!whatdoyouknow` command - `CMD_FORGETME_ENABLED` - Enable `!forgetme` command ## Commands ### User commands - `!setname ` - Set your preferred name - `!clearname` - Reset to Discord display name - `!remember ` - Tell the bot something about you - `!whatdoyouknow` - See what the bot remembers about you - `!forgetme` - Clear all facts about you - `!relationship` - See your relationship level with the bot - `!mood` - See the bot's current emotional state - `!botstats` - Bot shares its self-awareness statistics - `!ourhistory` - See your history with the bot - `!birthday ` - Set your birthday for the bot to remember ### Admin commands - `!setusername @user ` - Set name for another user - `!teachbot @user ` - Add a fact about a user ## Development Guidelines ### When Adding New Features 1. **Always write tests** - New services need corresponding test files in `tests/` 2. **Update documentation** - README.md and relevant docs/ files must be updated 3. **Update CLAUDE.md** - Add new services, models, and config options here 4. **Follow existing patterns** - Match the style of existing services ### Planned Features (In Progress) The following features are being implemented: 1. **Attachment Pattern Tracking** (`attachment_service.py`) - Detect anxious/avoidant/disorganized patterns - Adapt responses based on attachment state - Track what helps regulate each person 2. **Grief Journey Tracking** (`grief_service.py`) - Track grief context and phase - Recognize anniversaries and hard dates - Adjust support style based on grief phase 3. **Grounding & Coping Tools** (`grounding_service.py`) - Breathing exercises, sensory grounding - Spiral detection and intervention - Session pacing and intensity tracking 4. **Enhanced Support Memory** - Learn HOW someone wants to be supported - Track effective vs ineffective approaches - Remember comfort topics for breaks 5. **Communication Style Matching** - Energy matching (playful vs serious) - Directness calibration - Real-time tone adaptation