Files
loyal_companion/docs/living-ai/mood-system.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

339 lines
8.6 KiB
Markdown

# 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
```python
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:
```python
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:
```python
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:
```python
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
```python
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
```python
@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
```python
from loyal_companion.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()
```