- Fix config.py to ignore extra environment variables (docker-compose compatibility) - Create PortableJSON type for SQLite/PostgreSQL compatibility in tests - Replace JSONB and ARRAY types with PortableJSON in models - Add ensure_utc() helper to handle timezone-naive datetimes from SQLite - Fix timezone issues in mood_service, relationship_service, and self_awareness_service - Fix duplicate code in test_providers.py - Update CLAUDE.md with comprehensive Living AI documentation - Add testing section with commands and setup details - All 112 tests now pass successfully
8.3 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Build & Run Commands
# 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 daemon_boyfriend
# Run with Docker (includes PostgreSQL)
docker-compose up -d
# Run database migrations
alembic upgrade head
# Syntax check all Python files
python -m py_compile src/daemon_boyfriend/**/*.py
Testing
# Install dev dependencies
pip install -e ".[dev]"
# Run all tests
python -m pytest tests/ -v
# Run tests with coverage
python -m pytest tests/ --cov=daemon_boyfriend --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:
pytestwithpytest-asynciofor async test support- SQLite in-memory database for testing (via
aiosqlite) - Mock fixtures for Discord objects and AI providers in
tests/conftest.py
Architecture
This is a Discord bot that responds to @mentions with AI-generated responses (multi-provider support). It features a "Living AI" system that gives the bot personality, mood, and relationship tracking.
Provider Pattern
The AI system uses a provider abstraction pattern:
services/providers/base.pydefinesAIProviderabstract class withgenerate()methodservices/providers/openai.py,openrouter.py,anthropic.py,gemini.pyimplement the interfaceservices/ai_service.pyis the factory that creates the correct provider based onAI_PROVIDERenv var- OpenRouter uses OpenAI's client with a different base URL
- Gemini uses the
google-genaiSDK
Cog System
Discord functionality is in cogs/:
ai_chat.py-@mentionhandler (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 managementservices/user_service.py- User CRUD, custom names, facts managementservices/persistent_conversation.py- Database-backed conversation historyalembic/- Database migrations
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, etc.)
- 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 decayrelationship_service.py- Relationship scoring (stranger to close friend)fact_extraction_service.py- Autonomous fact learning from conversationsopinion_service.py- Bot develops opinions on topics over timeself_awareness_service.py- Bot statistics and self-reflectioncommunication_style_service.py- Learns user communication preferencesproactive_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 guildBotOpinion- Topic sentiments and preferencesUserRelationship- Per-user relationship scores and metricsUserCommunicationStyle- Learned communication preferencesScheduledEvent- Birthdays, follow-ups, remindersFactAssociation- Cross-user memory linksMoodHistory- 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
Relationship Levels
- Stranger (0-20): Polite, formal
- Acquaintance (21-40): Friendly but reserved
- Friend (41-60): Casual, warm
- Good Friend (61-80): Personal, references past talks
- Close Friend (81-100): Very casual, inside jokes
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.pyprovidesSearXNGServicefor web queriesai_chat.pyuses 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, andSEARXNG_MAX_RESULTSenv vars
Key Design Decisions
PersistentConversationManagerstores conversations in PostgreSQL whenDATABASE_URLis setConversationManageris the in-memory fallback when database is not configured- Long AI responses are split via
split_message()inai_chat.pyto respect Discord's 2000 char limit - The bot responds only to @mentions via
on_messagelistener - 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
PortableJSONtype inmodels/base.pyallows 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 containerSEARXNG_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.3)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.1)
Command Toggles
COMMANDS_ENABLED- Master switch for all commands (default: true)CMD_RELATIONSHIP_ENABLED- Enable!relationshipcommandCMD_MOOD_ENABLED- Enable!moodcommandCMD_BOTSTATS_ENABLED- Enable!botstatscommandCMD_OURHISTORY_ENABLED- Enable!ourhistorycommandCMD_BIRTHDAY_ENABLED- Enable!birthdaycommandCMD_REMEMBER_ENABLED- Enable!remembercommandCMD_SETNAME_ENABLED- Enable!setnamecommandCMD_WHATDOYOUKNOW_ENABLED- Enable!whatdoyouknowcommandCMD_FORGETME_ENABLED- Enable!forgetmecommand
Commands
User commands
!setname <name>- Set your preferred name!clearname- Reset to Discord display name!remember <fact>- 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 <date>- Set your birthday for the bot to remember
Admin commands
!setusername @user <name>- Set name for another user!teachbot @user <fact>- Add a fact about a user