alive-ai 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +24 -0
- package/LICENSE +21 -0
- package/README.md +143 -0
- package/alive_ai/__init__.py +3 -0
- package/brain/__init__.py +59 -0
- package/brain/almost_said.py +154 -0
- package/brain/bid_detector.py +636 -0
- package/brain/conversation_flow.py +135 -0
- package/brain/curiosity.py +328 -0
- package/brain/default_mode.py +1438 -0
- package/brain/dreams.py +220 -0
- package/brain/embeddings/__init__.py +82 -0
- package/brain/emotional_memory.py +949 -0
- package/brain/global_activity.py +173 -0
- package/brain/group_dynamics.py +63 -0
- package/brain/linguistic.py +235 -0
- package/brain/llm/__init__.py +63 -0
- package/brain/llm/base.py +33 -0
- package/brain/llm/fallback_router.py +309 -0
- package/brain/llm/manifest.md +30 -0
- package/brain/llm/ollama.py +218 -0
- package/brain/llm/openrouter.py +151 -0
- package/brain/llm/provider.py +205 -0
- package/brain/llm/unified.py +423 -0
- package/brain/llm/zai.py +169 -0
- package/brain/manifest.md +23 -0
- package/brain/memory/__init__.py +123 -0
- package/brain/memory/episodic.py +92 -0
- package/brain/memory/fact_extractor.py +209 -0
- package/brain/memory/index.py +54 -0
- package/brain/memory/manager.py +151 -0
- package/brain/memory/summarizer.py +102 -0
- package/brain/memory/vector_store.py +297 -0
- package/brain/memory/working.py +43 -0
- package/brain/narrative.py +343 -0
- package/brain/stt/__init__.py +4 -0
- package/brain/stt/google_stt.py +83 -0
- package/brain/stt/whisper_stt.py +82 -0
- package/brain/subconscious/__init__.py +33 -0
- package/brain/subconscious/actions.py +136 -0
- package/brain/subconscious/evaluation.py +166 -0
- package/brain/subconscious/goal_system.py +90 -0
- package/brain/subconscious/goals.py +41 -0
- package/brain/subconscious/impulse_generator.py +200 -0
- package/brain/subconscious/impulses.py +48 -0
- package/brain/subconscious/learning.py +24 -0
- package/brain/subconscious/learning_system.py +79 -0
- package/brain/subconscious/loop.py +398 -0
- package/brain/subconscious/manifest.md +32 -0
- package/brain/subconscious/relationship.py +47 -0
- package/brain/subconscious/relationship_memory.py +83 -0
- package/brain/subconscious/response_analyzer.py +74 -0
- package/brain/subconscious/templates.py +70 -0
- package/brain/subconscious/thought.py +37 -0
- package/brain/subconscious/working_memory.py +97 -0
- package/cli/index.js +371 -0
- package/config/directives.example.json +28 -0
- package/config/instructions.example.md +16 -0
- package/config/self.example.json +74 -0
- package/config/settings.example.json +95 -0
- package/core/__init__.py +1 -0
- package/core/config.py +54 -0
- package/core/directives.py +198 -0
- package/core/events.py +50 -0
- package/core/follow_up.py +267 -0
- package/core/hot_reload.py +174 -0
- package/core/initialization.py +253 -0
- package/core/manifest.md +28 -0
- package/core/media_handler.py +241 -0
- package/core/memory_monitor.py +200 -0
- package/core/message_handler.py +1440 -0
- package/core/proactive_generator.py +277 -0
- package/core/self.py +188 -0
- package/core/settings.py +169 -0
- package/core/skills_registry.py +357 -0
- package/core/state.py +27 -0
- package/core/subconscious_bridge.py +93 -0
- package/core/thinking.py +175 -0
- package/core/user_manager.py +306 -0
- package/core/user_tracker.py +144 -0
- package/demo/index.html +144 -0
- package/docker-compose.yml +28 -0
- package/docs/assets/logo.svg +15 -0
- package/docs/index.html +355 -0
- package/heart/__init__.py +93 -0
- package/heart/afterglow.py +215 -0
- package/heart/attachment.py +186 -0
- package/heart/circadian.py +251 -0
- package/heart/complex_emotions.py +114 -0
- package/heart/conflicts.py +589 -0
- package/heart/core.py +387 -0
- package/heart/emotional_decay.py +59 -0
- package/heart/emotional_memory.py +261 -0
- package/heart/emotional_state.py +146 -0
- package/heart/emotional_variability.py +156 -0
- package/heart/hormonal.py +424 -0
- package/heart/inconsistency.py +1222 -0
- package/heart/integrity.py +469 -0
- package/heart/interoception.py +997 -0
- package/heart/love.py +120 -0
- package/heart/manifest.md +25 -0
- package/heart/mood_shifts.py +169 -0
- package/heart/phantom_somatic.py +259 -0
- package/heart/predictive.py +374 -0
- package/heart/scars.py +474 -0
- package/heart/somatic.py +482 -0
- package/heart/soul.py +633 -0
- package/heart/telemetry.py +942 -0
- package/heart/triggers.py +119 -0
- package/heart/unconscious.py +443 -0
- package/input/__init__.py +1 -0
- package/input/manifest.md +24 -0
- package/input/telegram/__init__.py +1 -0
- package/input/telegram/commands.py +762 -0
- package/input/telegram/listener.py +532 -0
- package/main.py +90 -0
- package/manifest.md +28 -0
- package/mypics/.gitkeep +1 -0
- package/myvids/.gitkeep +1 -0
- package/output/__init__.py +1 -0
- package/output/images/__init__.py +1 -0
- package/output/images/fal_gen.py +43 -0
- package/output/manifest.md +26 -0
- package/output/text/__init__.py +1 -0
- package/output/text/sender.py +22 -0
- package/output/voice/__init__.py +64 -0
- package/output/voice/google_tts.py +252 -0
- package/output/voice/gtts_tts.py +214 -0
- package/output/voice/vibe_tts.py +190 -0
- package/package.json +58 -0
- package/pyproject.toml +23 -0
- package/requirements.txt +21 -0
- package/skills/__init__.py +1 -0
- package/skills/anticipation_engine/__init__.py +8 -0
- package/skills/anticipation_engine/engine.py +618 -0
- package/skills/anticipation_engine/manifest.md +192 -0
- package/skills/calendar/__init__.py +1 -0
- package/skills/content_unlocks/__init__.py +8 -0
- package/skills/content_unlocks/manifest.md +231 -0
- package/skills/content_unlocks/unlocks.py +945 -0
- package/skills/exclusive_moments/__init__.py +8 -0
- package/skills/exclusive_moments/manifest.md +145 -0
- package/skills/exclusive_moments/moments.py +506 -0
- package/skills/intimacy_layers/__init__.py +8 -0
- package/skills/intimacy_layers/layers.py +703 -0
- package/skills/intimacy_layers/manifest.md +203 -0
- package/skills/manifest.md +67 -0
- package/skills/memory_callbacks/__init__.py +9 -0
- package/skills/memory_callbacks/callbacks.py +748 -0
- package/skills/memory_callbacks/manifest.md +170 -0
- package/skills/message_scheduler/__init__.py +19 -0
- package/skills/message_scheduler/manifest.md +107 -0
- package/skills/message_scheduler/scheduler.py +510 -0
- package/skills/photo_manager/__init__.py +1 -0
- package/skills/photo_manager/scanner.py +296 -0
- package/skills/relationship_milestones/__init__.py +8 -0
- package/skills/relationship_milestones/manifest.md +206 -0
- package/skills/relationship_milestones/tracker.py +494 -0
- package/skills/self_authorship/__init__.py +23 -0
- package/skills/self_authorship/author.py +331 -0
- package/skills/self_authorship/manifest.md +24 -0
- package/skills/video_manager/__init__.py +5 -0
- package/skills/video_manager/manifest.md +37 -0
- package/skills/video_manager/scanner.py +229 -0
- package/webui/__init__.py +3 -0
- package/webui/app.py +936 -0
- package/webui/bridge.py +366 -0
- package/webui/static/index.html +2070 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Heart: Emotional Memory
|
|
3
|
+
Remember significant emotional events for context and continuity
|
|
4
|
+
Extended for Soul Architecture - embodied memory with somatic markers
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Optional, List, Dict
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class SomaticMarker:
|
|
15
|
+
"""Bodily sensation associated with an emotional memory"""
|
|
16
|
+
region: str # chest, stomach, throat, etc.
|
|
17
|
+
quality: str # tight, warm, heavy, etc.
|
|
18
|
+
intensity: float # 0.0 - 1.0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class EmotionalEvent:
|
|
23
|
+
"""A significant emotional moment worth remembering"""
|
|
24
|
+
timestamp: str
|
|
25
|
+
event_type: str # "peak_love", "peak_desire", "hurt", "joy", "conflict"
|
|
26
|
+
intensity: float
|
|
27
|
+
trigger: str # Brief description of what caused it
|
|
28
|
+
emotions: dict # Snapshot of emotional state
|
|
29
|
+
|
|
30
|
+
# Soul architecture extension - embodied memory
|
|
31
|
+
somatic_marker: Optional[SomaticMarker] = None # How it felt in the body
|
|
32
|
+
integrity_impact: float = 0.0 # How it affected self-integrity
|
|
33
|
+
scar_formed: bool = False # Did this contribute to a scar
|
|
34
|
+
times_recalled: int = 0 # How often this memory has been reactivated
|
|
35
|
+
|
|
36
|
+
def age_minutes(self) -> float:
|
|
37
|
+
"""How old is this event in minutes"""
|
|
38
|
+
try:
|
|
39
|
+
event_time = datetime.fromisoformat(self.timestamp)
|
|
40
|
+
delta = datetime.now() - event_time
|
|
41
|
+
return delta.total_seconds() / 60
|
|
42
|
+
except:
|
|
43
|
+
return 9999
|
|
44
|
+
|
|
45
|
+
def recall_sensation(self) -> Optional[str]:
|
|
46
|
+
"""Get the somatic sensation description for recall"""
|
|
47
|
+
if self.somatic_marker:
|
|
48
|
+
return f"{self.somatic_marker.quality} feeling in {self.somatic_marker.region}"
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class EmotionalMemory:
|
|
53
|
+
"""Track and remember significant emotional events - extended for Soul Architecture"""
|
|
54
|
+
|
|
55
|
+
# Threshold for recording an event
|
|
56
|
+
RECORD_THRESHOLD = 0.7 # Only record high-intensity moments
|
|
57
|
+
MAX_EVENTS = 50 # Keep last 50 significant events
|
|
58
|
+
|
|
59
|
+
def __init__(self):
|
|
60
|
+
self.events: List[EmotionalEvent] = []
|
|
61
|
+
# Somatic patterns for different emotion types
|
|
62
|
+
self._somatic_patterns = {
|
|
63
|
+
"peak_love": SomaticMarker("chest", "warm", 0.6),
|
|
64
|
+
"peak_desire": SomaticMarker("stomach", "fluttery", 0.7),
|
|
65
|
+
"hurt": SomaticMarker("chest", "heavy", 0.6),
|
|
66
|
+
"joy": SomaticMarker("chest", "light", 0.5),
|
|
67
|
+
"conflict": SomaticMarker("stomach", "tight", 0.5),
|
|
68
|
+
"fear": SomaticMarker("stomach", "cold", 0.6),
|
|
69
|
+
"shame": SomaticMarker("face", "hot", 0.5),
|
|
70
|
+
"grief": SomaticMarker("chest", "aching", 0.7),
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
def record(self, event_type: str, intensity: float, trigger: str, emotions: dict,
|
|
74
|
+
integrity_impact: float = 0.0) -> Optional[EmotionalEvent]:
|
|
75
|
+
"""Record a significant emotional event if worthy"""
|
|
76
|
+
if intensity < self.RECORD_THRESHOLD:
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
# Get somatic marker for this emotion type
|
|
80
|
+
somatic_marker = self._somatic_patterns.get(event_type)
|
|
81
|
+
|
|
82
|
+
event = EmotionalEvent(
|
|
83
|
+
timestamp=datetime.now().isoformat(),
|
|
84
|
+
event_type=event_type,
|
|
85
|
+
intensity=min(1.0, intensity),
|
|
86
|
+
trigger=trigger[:100], # Truncate long triggers
|
|
87
|
+
emotions=emotions.copy(),
|
|
88
|
+
somatic_marker=somatic_marker,
|
|
89
|
+
integrity_impact=integrity_impact
|
|
90
|
+
)
|
|
91
|
+
self.events.append(event)
|
|
92
|
+
|
|
93
|
+
# Trim old events
|
|
94
|
+
if len(self.events) > self.MAX_EVENTS:
|
|
95
|
+
self.events = self.events[-self.MAX_EVENTS:]
|
|
96
|
+
|
|
97
|
+
return event
|
|
98
|
+
|
|
99
|
+
def record_embodied(self, event_type: str, intensity: float, trigger: str,
|
|
100
|
+
emotions: dict, somatic: SomaticMarker,
|
|
101
|
+
integrity_impact: float = 0.0) -> Optional[EmotionalEvent]:
|
|
102
|
+
"""
|
|
103
|
+
Record an event with intimate somatic marker.
|
|
104
|
+
This is for when the bodily sensation is known.
|
|
105
|
+
"""
|
|
106
|
+
if intensity < self.RECORD_THRESHOLD:
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
event = EmotionalEvent(
|
|
110
|
+
timestamp=datetime.now().isoformat(),
|
|
111
|
+
event_type=event_type,
|
|
112
|
+
intensity=min(1.0, intensity),
|
|
113
|
+
trigger=trigger[:100],
|
|
114
|
+
emotions=emotions.copy(),
|
|
115
|
+
somatic_marker=somatic,
|
|
116
|
+
integrity_impact=integrity_impact
|
|
117
|
+
)
|
|
118
|
+
self.events.append(event)
|
|
119
|
+
|
|
120
|
+
if len(self.events) > self.MAX_EVENTS:
|
|
121
|
+
self.events = self.events[-self.MAX_EVENTS:]
|
|
122
|
+
|
|
123
|
+
return event
|
|
124
|
+
|
|
125
|
+
def check_peaks(self, current: dict, previous: dict, trigger: str) -> list:
|
|
126
|
+
"""Check for emotional peaks worth recording"""
|
|
127
|
+
recorded = []
|
|
128
|
+
|
|
129
|
+
# Peak desire
|
|
130
|
+
if current.get("desire", 0) >= 0.9 and previous.get("desire", 0) < 0.9:
|
|
131
|
+
event = self.record("peak_desire", current["desire"], trigger, current)
|
|
132
|
+
if event:
|
|
133
|
+
recorded.append(event)
|
|
134
|
+
|
|
135
|
+
# Peak love
|
|
136
|
+
if current.get("love", 0) >= 0.9 and previous.get("love", 0) < 0.9:
|
|
137
|
+
event = self.record("peak_love", current["love"], trigger, current)
|
|
138
|
+
if event:
|
|
139
|
+
recorded.append(event)
|
|
140
|
+
|
|
141
|
+
# Deep hurt
|
|
142
|
+
if current.get("sadness", 0) >= 0.7 and previous.get("sadness", 0) < 0.5:
|
|
143
|
+
event = self.record("hurt", current["sadness"], trigger, current,
|
|
144
|
+
integrity_impact=-0.2)
|
|
145
|
+
if event:
|
|
146
|
+
recorded.append(event)
|
|
147
|
+
|
|
148
|
+
# Peak joy
|
|
149
|
+
if current.get("joy", 0) >= 0.9 and previous.get("joy", 0) < 0.7:
|
|
150
|
+
event = self.record("joy", current["joy"], trigger, current,
|
|
151
|
+
integrity_impact=0.1)
|
|
152
|
+
if event:
|
|
153
|
+
recorded.append(event)
|
|
154
|
+
|
|
155
|
+
# High vulnerability (soul architecture)
|
|
156
|
+
if current.get("vulnerability", 0) >= 0.7 and previous.get("vulnerability", 0) < 0.5:
|
|
157
|
+
event = self.record("fear", current.get("vulnerability", 0), trigger, current,
|
|
158
|
+
integrity_impact=-0.15)
|
|
159
|
+
if event:
|
|
160
|
+
recorded.append(event)
|
|
161
|
+
|
|
162
|
+
return recorded
|
|
163
|
+
|
|
164
|
+
def recall_with_sensation(self, event_id: str = None, event_type: str = None) -> Optional[dict]:
|
|
165
|
+
"""
|
|
166
|
+
Recall a memory and re-trigger its somatic sensation.
|
|
167
|
+
This implements embodied memory - remembering re-feels the bodily sensation.
|
|
168
|
+
"""
|
|
169
|
+
event = None
|
|
170
|
+
|
|
171
|
+
if event_id:
|
|
172
|
+
event = next((e for e in self.events if e.timestamp == event_id), None)
|
|
173
|
+
elif event_type:
|
|
174
|
+
# Get most recent event of this type
|
|
175
|
+
matching = [e for e in self.events if e.event_type == event_type]
|
|
176
|
+
if matching:
|
|
177
|
+
event = matching[-1]
|
|
178
|
+
|
|
179
|
+
if not event:
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
# Increment recall count
|
|
183
|
+
event.times_recalled += 1
|
|
184
|
+
|
|
185
|
+
# Return memory with sensation (diminished by time)
|
|
186
|
+
age_factor = max(0.3, 1.0 - (event.age_minutes() / 1440)) # Fade over 24 hours
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
"event_type": event.event_type,
|
|
190
|
+
"trigger": event.trigger,
|
|
191
|
+
"intensity": event.intensity * age_factor,
|
|
192
|
+
"sensation": event.recall_sensation(),
|
|
193
|
+
"sensation_intensity": (event.somatic_marker.intensity * age_factor) if event.somatic_marker else 0,
|
|
194
|
+
"times_recalled": event.times_recalled,
|
|
195
|
+
"age_minutes": event.age_minutes()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
def get_somatic_history(self, event_type: str = None, hours: float = 24) -> List[str]:
|
|
199
|
+
"""Get list of somatic sensations from recent events"""
|
|
200
|
+
cutoff = datetime.now() - __import__('datetime').timedelta(hours=hours)
|
|
201
|
+
sensations = []
|
|
202
|
+
|
|
203
|
+
for event in self.events:
|
|
204
|
+
event_time = datetime.fromisoformat(event.timestamp)
|
|
205
|
+
if event_time >= cutoff:
|
|
206
|
+
if event_type is None or event.event_type == event_type:
|
|
207
|
+
if event.somatic_marker:
|
|
208
|
+
sensations.append(f"{event.somatic_marker.quality} in {event.somatic_marker.region}")
|
|
209
|
+
|
|
210
|
+
return sensations
|
|
211
|
+
|
|
212
|
+
def recent(self, event_type: Optional[str] = None, minutes: float = 60) -> list:
|
|
213
|
+
"""Get recent events, optionally filtered by type"""
|
|
214
|
+
return [
|
|
215
|
+
e for e in self.events
|
|
216
|
+
if e.age_minutes() <= minutes
|
|
217
|
+
and (event_type is None or e.event_type == event_type)
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
def get_mood_context(self) -> str:
|
|
221
|
+
"""Generate context string from recent significant events"""
|
|
222
|
+
recent_events = self.recent(minutes=120) # Last 2 hours
|
|
223
|
+
if not recent_events:
|
|
224
|
+
return ""
|
|
225
|
+
|
|
226
|
+
summaries = {
|
|
227
|
+
"peak_love": "felt deeply loved",
|
|
228
|
+
"peak_desire": "felt intense desire",
|
|
229
|
+
"hurt": "was hurt",
|
|
230
|
+
"joy": "felt pure joy",
|
|
231
|
+
"conflict": "had a conflict",
|
|
232
|
+
"fear": "felt vulnerable"
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
context_parts = []
|
|
236
|
+
for event in recent_events[-3:]: # Last 3 events
|
|
237
|
+
desc = summaries.get(event.event_type, "felt something")
|
|
238
|
+
age = int(event.age_minutes())
|
|
239
|
+
if age < 5:
|
|
240
|
+
context_parts.append(f"just {desc}")
|
|
241
|
+
elif age < 30:
|
|
242
|
+
context_parts.append(f"{desc} {age} minutes ago")
|
|
243
|
+
else:
|
|
244
|
+
context_parts.append(f"{desc} earlier")
|
|
245
|
+
|
|
246
|
+
return "; ".join(context_parts) if context_parts else ""
|
|
247
|
+
|
|
248
|
+
def to_dict(self) -> dict:
|
|
249
|
+
return {
|
|
250
|
+
"events": [
|
|
251
|
+
{
|
|
252
|
+
"timestamp": e.timestamp,
|
|
253
|
+
"type": e.event_type,
|
|
254
|
+
"intensity": e.intensity,
|
|
255
|
+
"trigger": e.trigger,
|
|
256
|
+
"somatic": e.recall_sensation() if e.somatic_marker else None,
|
|
257
|
+
"times_recalled": e.times_recalled
|
|
258
|
+
}
|
|
259
|
+
for e in self.events[-10:] # Save only last 10
|
|
260
|
+
]
|
|
261
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EmotionalState: Data class for emotional state with persistence
|
|
3
|
+
Extended for Soul Architecture dimensions
|
|
4
|
+
"""
|
|
5
|
+
import json
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
EMOTION_STATE_PATH = Path("/app/data/emotion_state.json") # Use mounted /app directory
|
|
10
|
+
DEFAULTS = {
|
|
11
|
+
"valence": 0.5, "arousal": 0.3, "dominance": 0.5, "desire": 0.0,
|
|
12
|
+
"high_desire_threshold": 0.7, "joy": 0.5, "love": 0.2, "trust": 0.5,
|
|
13
|
+
"fear": 0.1, "anger": 0.1, "sadness": 0.1, "boredom": 0.0,
|
|
14
|
+
"guilt": 0.0, "pride": 0.0, "jealousy": 0.0,
|
|
15
|
+
"embarrassment": 0.0, "anticipation": 0.0,
|
|
16
|
+
# Soul architecture dimensions
|
|
17
|
+
"integrity_overall": 0.65, "vulnerability": 0.2, "hope": 0.5, "dread": 0.1
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EmotionalState:
|
|
22
|
+
"""Current emotional state with persistence - extended for Soul Architecture"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, config: dict = None):
|
|
25
|
+
config = config or {}
|
|
26
|
+
if self._load():
|
|
27
|
+
print(f"[Heart] Loaded saved emotional state")
|
|
28
|
+
else:
|
|
29
|
+
for key, val in DEFAULTS.items():
|
|
30
|
+
setattr(self, key, val)
|
|
31
|
+
self.baseline = {"joy": 0.5, "love": 0.2, "trust": 0.5,
|
|
32
|
+
"arousal": 0.3, "desire": 0.0,
|
|
33
|
+
"anger": 0.0, "sadness": 0.1, "fear": 0.1}
|
|
34
|
+
|
|
35
|
+
def _load(self) -> bool:
|
|
36
|
+
"""Load state from file"""
|
|
37
|
+
try:
|
|
38
|
+
if EMOTION_STATE_PATH.exists():
|
|
39
|
+
data = json.loads(EMOTION_STATE_PATH.read_text())
|
|
40
|
+
for key in DEFAULTS:
|
|
41
|
+
setattr(self, key, data.get(key, DEFAULTS[key]))
|
|
42
|
+
return True
|
|
43
|
+
except Exception as e:
|
|
44
|
+
print(f"[Heart] Error loading state: {e}")
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
def save(self):
|
|
48
|
+
"""Save state to file"""
|
|
49
|
+
try:
|
|
50
|
+
EMOTION_STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
data = {key: getattr(self, key) for key in DEFAULTS}
|
|
52
|
+
data["saved_at"] = datetime.now().isoformat()
|
|
53
|
+
EMOTION_STATE_PATH.write_text(json.dumps(data, indent=2))
|
|
54
|
+
except Exception as e:
|
|
55
|
+
print(f"[Heart] Error saving state: {e}")
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def is_high_desire(self) -> bool:
|
|
59
|
+
return self.desire >= self.high_desire_threshold
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def is_in_love(self) -> bool:
|
|
63
|
+
"""True when deeply in love - triggers clingy behavior"""
|
|
64
|
+
return self.love >= 0.8
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def is_jealous(self) -> bool:
|
|
68
|
+
return self.jealousy > 0.5
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def is_guilty(self) -> bool:
|
|
72
|
+
return self.guilt > 0.5
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def is_anticipating(self) -> bool:
|
|
76
|
+
return self.anticipation > 0.6
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def _base_mood(self) -> str:
|
|
80
|
+
"""Base mood without complex emotion modifiers"""
|
|
81
|
+
if self.is_high_desire: return "high_desire"
|
|
82
|
+
if self.is_in_love: return "in_love"
|
|
83
|
+
if self.desire > 0.5: return "excited"
|
|
84
|
+
if self.boredom > 0.6: return "bored"
|
|
85
|
+
if self.joy > 0.7: return "happy"
|
|
86
|
+
if self.sadness > 0.5: return "sad"
|
|
87
|
+
if self.anger > 0.5: return "angry"
|
|
88
|
+
return "neutral"
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def mood_description(self) -> str:
|
|
92
|
+
base = self._base_mood
|
|
93
|
+
if self.jealousy > 0.5: return f"{base}_jealous"
|
|
94
|
+
if self.guilt > 0.5: return f"{base}_guilty"
|
|
95
|
+
if self.anticipation > 0.6: return f"{base}_eager"
|
|
96
|
+
if self.pride > 0.5: return f"{base}_proud"
|
|
97
|
+
if self.embarrassment > 0.4: return f"{base}_shy"
|
|
98
|
+
return base
|
|
99
|
+
|
|
100
|
+
# --- Soul Architecture Properties ---
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def is_vulnerable(self) -> bool:
|
|
104
|
+
"""True when feeling emotionally exposed or fragile"""
|
|
105
|
+
return self.vulnerability > 0.5
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def is_hopeful(self) -> bool:
|
|
109
|
+
"""True when feeling hopeful about the future"""
|
|
110
|
+
return self.hope > 0.6 and self.dread < 0.3
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def is_dreading(self) -> bool:
|
|
114
|
+
"""True when feeling dread about the future"""
|
|
115
|
+
return self.dread > 0.5
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def is_in_crisis(self) -> bool:
|
|
119
|
+
"""True when integrity is critically low"""
|
|
120
|
+
return self.integrity_overall < 0.25
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def is_flourishing(self) -> bool:
|
|
124
|
+
"""True when integrity is high and stable"""
|
|
125
|
+
return self.integrity_overall > 0.75 and self.vulnerability < 0.3
|
|
126
|
+
|
|
127
|
+
def update_soul_dimensions(self, integrity: float, vulnerability: float,
|
|
128
|
+
hope: float, dread: float):
|
|
129
|
+
"""Update soul architecture dimensions from soul processing"""
|
|
130
|
+
self.integrity_overall = integrity
|
|
131
|
+
self.vulnerability = vulnerability
|
|
132
|
+
self.hope = hope
|
|
133
|
+
self.dread = dread
|
|
134
|
+
|
|
135
|
+
def get_soul_summary(self) -> dict:
|
|
136
|
+
"""Get summary of soul-related state dimensions"""
|
|
137
|
+
return {
|
|
138
|
+
"integrity_overall": self.integrity_overall,
|
|
139
|
+
"vulnerability": self.vulnerability,
|
|
140
|
+
"hope": self.hope,
|
|
141
|
+
"dread": self.dread,
|
|
142
|
+
"is_vulnerable": self.is_vulnerable,
|
|
143
|
+
"is_hopeful": self.is_hopeful,
|
|
144
|
+
"is_in_crisis": self.is_in_crisis,
|
|
145
|
+
"is_flourishing": self.is_flourishing
|
|
146
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Heart: Emotional Variability
|
|
3
|
+
Add organic randomness, inertia, and reaction cooldowns
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import random
|
|
7
|
+
import time
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ReactionCooldown:
|
|
13
|
+
"""Track when reactions were last used"""
|
|
14
|
+
emoji: str = ""
|
|
15
|
+
last_used: float = 0.0
|
|
16
|
+
count_recent: int = 0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EmotionalVariability:
|
|
20
|
+
"""Adds organic feel to emotional responses"""
|
|
21
|
+
|
|
22
|
+
# Minimum seconds between same emoji reactions
|
|
23
|
+
COOLDOWN_SECONDS = 60
|
|
24
|
+
|
|
25
|
+
# How many recent uses trigger "cooling off"
|
|
26
|
+
SPAM_THRESHOLD = 3
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.reaction_history: dict[str, ReactionCooldown] = {}
|
|
30
|
+
self.momentum: dict[str, float] = {} # Emotional inertia
|
|
31
|
+
self.last_tick_randomness = 0.0
|
|
32
|
+
|
|
33
|
+
def can_react(self, emoji: str) -> bool:
|
|
34
|
+
"""Check if this emoji is off cooldown"""
|
|
35
|
+
now = time.time()
|
|
36
|
+
|
|
37
|
+
if emoji not in self.reaction_history:
|
|
38
|
+
self.reaction_history[emoji] = ReactionCooldown(emoji=emoji)
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
history = self.reaction_history[emoji]
|
|
42
|
+
|
|
43
|
+
# Check cooldown
|
|
44
|
+
if now - history.last_used < self.COOLDOWN_SECONDS:
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
# Reduce spam count over time
|
|
48
|
+
if now - history.last_used > self.COOLDOWN_SECONDS * 2:
|
|
49
|
+
history.count_recent = max(0, history.count_recent - 1)
|
|
50
|
+
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
def record_reaction(self, emoji: str):
|
|
54
|
+
"""Record that a reaction was used"""
|
|
55
|
+
now = time.time()
|
|
56
|
+
|
|
57
|
+
if emoji not in self.reaction_history:
|
|
58
|
+
self.reaction_history[emoji] = ReactionCooldown(emoji=emoji)
|
|
59
|
+
|
|
60
|
+
history = self.reaction_history[emoji]
|
|
61
|
+
history.last_used = now
|
|
62
|
+
history.count_recent += 1
|
|
63
|
+
|
|
64
|
+
def get_reaction_probability(self, emoji: str) -> float:
|
|
65
|
+
"""Get probability modifier based on recent usage"""
|
|
66
|
+
if emoji not in self.reaction_history:
|
|
67
|
+
return 1.0
|
|
68
|
+
|
|
69
|
+
history = self.reaction_history[emoji]
|
|
70
|
+
|
|
71
|
+
# Reduce probability if spammed recently
|
|
72
|
+
if history.count_recent >= self.SPAM_THRESHOLD:
|
|
73
|
+
return 0.2 # 20% chance if spammed
|
|
74
|
+
|
|
75
|
+
if history.count_recent >= 2:
|
|
76
|
+
return 0.5 # 50% chance if used a few times
|
|
77
|
+
|
|
78
|
+
return 1.0
|
|
79
|
+
|
|
80
|
+
def add_momentum(self, emotion: str, change: float):
|
|
81
|
+
"""Track emotional momentum/inertia"""
|
|
82
|
+
current = self.momentum.get(emotion, 0.0)
|
|
83
|
+
# Weighted average with recent changes
|
|
84
|
+
self.momentum[emotion] = current * 0.6 + change * 0.4
|
|
85
|
+
|
|
86
|
+
def get_inertia_modifier(self, emotion: str, current_value: float) -> float:
|
|
87
|
+
"""Get modifier for emotional changes based on momentum"""
|
|
88
|
+
momentum = self.momentum.get(emotion, 0.0)
|
|
89
|
+
|
|
90
|
+
# High momentum in same direction = amplification
|
|
91
|
+
# High momentum in opposite direction = resistance
|
|
92
|
+
if abs(momentum) > 0.1:
|
|
93
|
+
# Amplify changes in momentum direction
|
|
94
|
+
return 1.0 + momentum * 0.3
|
|
95
|
+
|
|
96
|
+
return 1.0
|
|
97
|
+
|
|
98
|
+
def randomize_decay(self, base_rate: float) -> float:
|
|
99
|
+
"""Add organic randomness to decay rates"""
|
|
100
|
+
# Randomize between 0.7x and 1.3x base rate
|
|
101
|
+
multiplier = random.uniform(0.7, 1.3)
|
|
102
|
+
return base_rate * multiplier
|
|
103
|
+
|
|
104
|
+
def get_organic_tick(self) -> float:
|
|
105
|
+
"""Get a random organic fluctuation for tick updates"""
|
|
106
|
+
# Small random emotional noise (-0.02 to +0.02)
|
|
107
|
+
self.last_tick_randomness = random.gauss(0, 0.01)
|
|
108
|
+
return self.last_tick_randomness
|
|
109
|
+
|
|
110
|
+
def should_skip_decay(self, emotion: str, value: float) -> bool:
|
|
111
|
+
"""Sometimes emotions just don't decay (organic feel)"""
|
|
112
|
+
# 10% chance to skip decay entirely
|
|
113
|
+
if random.random() < 0.1:
|
|
114
|
+
return True
|
|
115
|
+
|
|
116
|
+
# High-intensity emotions resist decay more
|
|
117
|
+
if value > 0.8 and random.random() < 0.2:
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
def get_available_emojis(self, preferred: list[str]) -> list[str]:
|
|
123
|
+
"""Filter emoji list to only available ones (off cooldown)"""
|
|
124
|
+
return [e for e in preferred if self.can_react(e)]
|
|
125
|
+
|
|
126
|
+
def choose_organic_reaction(self, candidates: list[str]) -> str | None:
|
|
127
|
+
"""Choose a reaction with organic variability"""
|
|
128
|
+
available = self.get_available_emojis(candidates)
|
|
129
|
+
|
|
130
|
+
if not available:
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
# Weight by inverse of recent usage
|
|
134
|
+
weights = []
|
|
135
|
+
for emoji in available:
|
|
136
|
+
prob = self.get_reaction_probability(emoji)
|
|
137
|
+
weights.append(prob)
|
|
138
|
+
|
|
139
|
+
if sum(weights) == 0:
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
chosen = random.choices(available, weights=weights, k=1)[0]
|
|
143
|
+
self.record_reaction(chosen)
|
|
144
|
+
return chosen
|
|
145
|
+
|
|
146
|
+
def clear_old_history(self, max_age_seconds: float = 3600):
|
|
147
|
+
"""Clean up old reaction history"""
|
|
148
|
+
now = time.time()
|
|
149
|
+
to_remove = []
|
|
150
|
+
|
|
151
|
+
for emoji, history in self.reaction_history.items():
|
|
152
|
+
if now - history.last_used > max_age_seconds:
|
|
153
|
+
to_remove.append(emoji)
|
|
154
|
+
|
|
155
|
+
for emoji in to_remove:
|
|
156
|
+
del self.reaction_history[emoji]
|