8.6 KiB
Mood System Deep Dive
The mood system gives the bot emotional states that evolve over time and affect how it responds to users.
Psychological Model
The mood system uses the Valence-Arousal Model from affective psychology:
High Arousal (+1)
│
Annoyed │ Excited
● │ ●
│
Curious │
● │
Low Valence ────────────────┼──────────────── High Valence
(-1) │ (+1)
│
Bored │ Happy
● │ ●
│
Calm │
● │
│
Low Arousal (-1)
Dimensions
Valence (-1 to +1)
- Represents the positive/negative quality of the emotional state
- -1 = Very negative (sad, frustrated, upset)
- 0 = Neutral
- +1 = Very positive (happy, joyful, content)
Arousal (-1 to +1)
- Represents the energy level or activation
- -1 = Very low energy (calm, sleepy, relaxed)
- 0 = Neutral energy
- +1 = Very high energy (excited, alert, agitated)
Mood Labels
The system classifies the current mood into seven labels:
| Label | Valence | Arousal | Description |
|---|---|---|---|
| Excited | > 0.3 | > 0.3 | High energy, positive emotions |
| Happy | > 0.3 | ≤ 0.3 | Positive but calm contentment |
| Calm | -0.3 to 0.3 | < -0.3 | Peaceful, serene state |
| Neutral | -0.3 to 0.3 | -0.3 to 0.3 | Baseline, unremarkable state |
| Bored | < -0.3 | ≤ 0.3 | Low engagement, understimulated |
| Annoyed | < -0.3 | > 0.3 | Frustrated, irritated |
| Curious | -0.3 to 0.3 | > 0.3 | Interested, engaged, questioning |
Classification Logic
def _classify_mood(valence: float, arousal: float) -> MoodLabel:
if valence > 0.3:
return MoodLabel.EXCITED if arousal > 0.3 else MoodLabel.HAPPY
elif valence < -0.3:
return MoodLabel.ANNOYED if arousal > 0.3 else MoodLabel.BORED
else:
if arousal > 0.3:
return MoodLabel.CURIOUS
elif arousal < -0.3:
return MoodLabel.CALM
return MoodLabel.NEUTRAL
Mood Intensity
Intensity measures how strong the current mood is:
intensity = (abs(valence) + abs(arousal)) / 2
- 0.0 - 0.2: Very weak, doesn't affect behavior
- 0.2 - 0.5: Moderate, subtle behavioral changes
- 0.5 - 0.7: Strong, noticeable behavioral changes
- 0.7 - 1.0: Very strong, significant behavioral changes
Time Decay
Mood naturally decays toward neutral over time:
hours_since_update = (now - last_update).total_seconds() / 3600
decay_factor = max(0, 1 - (decay_rate * hours_since_update))
current_valence = stored_valence * decay_factor
current_arousal = stored_arousal * decay_factor
Configuration:
MOOD_DECAY_RATE= 0.1 (default)- After 10 hours, mood is fully neutral
Decay Examples:
| Hours | Decay Factor | Effect |
|---|---|---|
| 0 | 1.0 | Full mood |
| 2 | 0.8 | 80% of mood remains |
| 5 | 0.5 | 50% of mood remains |
| 10 | 0.0 | Fully neutral |
Mood Updates
When an interaction occurs, mood is updated:
new_valence = current_valence + (sentiment_delta * 0.3)
new_arousal = current_arousal + (engagement_delta * 0.3)
Dampening (Inertia)
Changes are dampened by 70% (only 30% absorption):
- Prevents wild mood swings
- Creates emotional stability
- Makes mood feel more natural
Update Triggers
| Trigger Type | Sentiment Source | Engagement Source |
|---|---|---|
conversation |
Message sentiment | Message engagement |
event |
Event nature | Event importance |
time |
Scheduled | Scheduled |
Input Parameters
sentiment_delta (-1 to +1)
- Positive: Happy interactions, compliments, fun conversations
- Negative: Arguments, frustration, rude messages
- Derived from AI analysis or keyword detection
engagement_delta (-1 to +1)
- Positive: Long conversations, interesting topics, active engagement
- Negative: Short dismissive messages, ignored responses
- Derived from message length, conversation turns, topic interest
Prompt Modifiers
Based on current mood, the system generates prompt text:
Excited (High valence, High arousal)
You're feeling enthusiastic and energetic right now!
Be expressive, use exclamation marks, show genuine excitement.
Happy (High valence, Low arousal)
You're in a good mood - warm, friendly, and content.
Be positive and encouraging in your responses.
Calm (Neutral valence, Low arousal)
You're feeling peaceful and relaxed.
Respond thoughtfully and with a serene demeanor.
Neutral
(No modifier)
Bored (Low valence, Low arousal)
You're feeling a bit understimulated.
Keep responses shorter, maybe try to steer toward more interesting topics.
Annoyed (Low valence, High arousal)
You're slightly irritated.
Be a bit more terse, less patient with repetition or vague questions.
Curious (Neutral valence, High arousal)
You're feeling inquisitive and engaged!
Ask follow-up questions, show genuine interest in what the user is saying.
Intensity Prefix
For strong moods (intensity > 0.7):
[Strong mood] You're feeling enthusiastic...
Mood History
All mood changes are recorded in mood_history table:
| Field | Description |
|---|---|
guild_id |
Guild where mood changed |
valence |
New valence value |
arousal |
New arousal value |
trigger_type |
What caused the change |
trigger_user_id |
Who triggered it (if any) |
trigger_description |
Description of event |
recorded_at |
When change occurred |
This enables:
- Mood trend analysis
- Understanding what affects mood
- Debugging mood issues
- User impact tracking
Bot Statistics
The mood service also tracks global statistics:
| Statistic | Description |
|---|---|
total_messages_sent |
Lifetime message count |
total_facts_learned |
Facts extracted from conversations |
total_users_known |
Unique users interacted with |
first_activated_at |
Bot "birth date" |
Used for self-awareness and the !botstats command.
API Reference
MoodService
class MoodService:
def __init__(self, session: AsyncSession)
async def get_current_mood(
self,
guild_id: int | None = None
) -> MoodState
async def update_mood(
self,
guild_id: int | None,
sentiment_delta: float,
engagement_delta: float,
trigger_type: str,
trigger_user_id: int | None = None,
trigger_description: str | None = None,
) -> MoodState
async def increment_stats(
self,
guild_id: int | None,
messages_sent: int = 0,
facts_learned: int = 0,
users_known: int = 0,
) -> None
async def get_stats(
self,
guild_id: int | None = None
) -> dict
def get_mood_prompt_modifier(
self,
mood: MoodState
) -> str
MoodState
@dataclass
class MoodState:
valence: float # -1 to 1
arousal: float # -1 to 1
label: MoodLabel # Classified label
intensity: float # 0 to 1
Configuration
| Variable | Default | Description |
|---|---|---|
MOOD_ENABLED |
true |
Enable/disable mood system |
MOOD_DECAY_RATE |
0.1 |
Decay per hour toward neutral |
Example Usage
from daemon_boyfriend.services.mood_service import MoodService
async with get_session() as session:
mood_service = MoodService(session)
# Get current mood
mood = await mood_service.get_current_mood(guild_id=123)
print(f"Current mood: {mood.label.value} (intensity: {mood.intensity})")
# Update mood after interaction
new_mood = await mood_service.update_mood(
guild_id=123,
sentiment_delta=0.5, # Positive interaction
engagement_delta=0.3, # Moderately engaging
trigger_type="conversation",
trigger_user_id=456,
trigger_description="User shared good news"
)
# Get prompt modifier
modifier = mood_service.get_mood_prompt_modifier(new_mood)
print(f"Prompt modifier: {modifier}")
await session.commit()