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.
339 lines
8.6 KiB
Markdown
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()
|
|
```
|