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.
Files changed (168) hide show
  1. package/Dockerfile +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +143 -0
  4. package/alive_ai/__init__.py +3 -0
  5. package/brain/__init__.py +59 -0
  6. package/brain/almost_said.py +154 -0
  7. package/brain/bid_detector.py +636 -0
  8. package/brain/conversation_flow.py +135 -0
  9. package/brain/curiosity.py +328 -0
  10. package/brain/default_mode.py +1438 -0
  11. package/brain/dreams.py +220 -0
  12. package/brain/embeddings/__init__.py +82 -0
  13. package/brain/emotional_memory.py +949 -0
  14. package/brain/global_activity.py +173 -0
  15. package/brain/group_dynamics.py +63 -0
  16. package/brain/linguistic.py +235 -0
  17. package/brain/llm/__init__.py +63 -0
  18. package/brain/llm/base.py +33 -0
  19. package/brain/llm/fallback_router.py +309 -0
  20. package/brain/llm/manifest.md +30 -0
  21. package/brain/llm/ollama.py +218 -0
  22. package/brain/llm/openrouter.py +151 -0
  23. package/brain/llm/provider.py +205 -0
  24. package/brain/llm/unified.py +423 -0
  25. package/brain/llm/zai.py +169 -0
  26. package/brain/manifest.md +23 -0
  27. package/brain/memory/__init__.py +123 -0
  28. package/brain/memory/episodic.py +92 -0
  29. package/brain/memory/fact_extractor.py +209 -0
  30. package/brain/memory/index.py +54 -0
  31. package/brain/memory/manager.py +151 -0
  32. package/brain/memory/summarizer.py +102 -0
  33. package/brain/memory/vector_store.py +297 -0
  34. package/brain/memory/working.py +43 -0
  35. package/brain/narrative.py +343 -0
  36. package/brain/stt/__init__.py +4 -0
  37. package/brain/stt/google_stt.py +83 -0
  38. package/brain/stt/whisper_stt.py +82 -0
  39. package/brain/subconscious/__init__.py +33 -0
  40. package/brain/subconscious/actions.py +136 -0
  41. package/brain/subconscious/evaluation.py +166 -0
  42. package/brain/subconscious/goal_system.py +90 -0
  43. package/brain/subconscious/goals.py +41 -0
  44. package/brain/subconscious/impulse_generator.py +200 -0
  45. package/brain/subconscious/impulses.py +48 -0
  46. package/brain/subconscious/learning.py +24 -0
  47. package/brain/subconscious/learning_system.py +79 -0
  48. package/brain/subconscious/loop.py +398 -0
  49. package/brain/subconscious/manifest.md +32 -0
  50. package/brain/subconscious/relationship.py +47 -0
  51. package/brain/subconscious/relationship_memory.py +83 -0
  52. package/brain/subconscious/response_analyzer.py +74 -0
  53. package/brain/subconscious/templates.py +70 -0
  54. package/brain/subconscious/thought.py +37 -0
  55. package/brain/subconscious/working_memory.py +97 -0
  56. package/cli/index.js +371 -0
  57. package/config/directives.example.json +28 -0
  58. package/config/instructions.example.md +16 -0
  59. package/config/self.example.json +74 -0
  60. package/config/settings.example.json +95 -0
  61. package/core/__init__.py +1 -0
  62. package/core/config.py +54 -0
  63. package/core/directives.py +198 -0
  64. package/core/events.py +50 -0
  65. package/core/follow_up.py +267 -0
  66. package/core/hot_reload.py +174 -0
  67. package/core/initialization.py +253 -0
  68. package/core/manifest.md +28 -0
  69. package/core/media_handler.py +241 -0
  70. package/core/memory_monitor.py +200 -0
  71. package/core/message_handler.py +1440 -0
  72. package/core/proactive_generator.py +277 -0
  73. package/core/self.py +188 -0
  74. package/core/settings.py +169 -0
  75. package/core/skills_registry.py +357 -0
  76. package/core/state.py +27 -0
  77. package/core/subconscious_bridge.py +93 -0
  78. package/core/thinking.py +175 -0
  79. package/core/user_manager.py +306 -0
  80. package/core/user_tracker.py +144 -0
  81. package/demo/index.html +144 -0
  82. package/docker-compose.yml +28 -0
  83. package/docs/assets/logo.svg +15 -0
  84. package/docs/index.html +355 -0
  85. package/heart/__init__.py +93 -0
  86. package/heart/afterglow.py +215 -0
  87. package/heart/attachment.py +186 -0
  88. package/heart/circadian.py +251 -0
  89. package/heart/complex_emotions.py +114 -0
  90. package/heart/conflicts.py +589 -0
  91. package/heart/core.py +387 -0
  92. package/heart/emotional_decay.py +59 -0
  93. package/heart/emotional_memory.py +261 -0
  94. package/heart/emotional_state.py +146 -0
  95. package/heart/emotional_variability.py +156 -0
  96. package/heart/hormonal.py +424 -0
  97. package/heart/inconsistency.py +1222 -0
  98. package/heart/integrity.py +469 -0
  99. package/heart/interoception.py +997 -0
  100. package/heart/love.py +120 -0
  101. package/heart/manifest.md +25 -0
  102. package/heart/mood_shifts.py +169 -0
  103. package/heart/phantom_somatic.py +259 -0
  104. package/heart/predictive.py +374 -0
  105. package/heart/scars.py +474 -0
  106. package/heart/somatic.py +482 -0
  107. package/heart/soul.py +633 -0
  108. package/heart/telemetry.py +942 -0
  109. package/heart/triggers.py +119 -0
  110. package/heart/unconscious.py +443 -0
  111. package/input/__init__.py +1 -0
  112. package/input/manifest.md +24 -0
  113. package/input/telegram/__init__.py +1 -0
  114. package/input/telegram/commands.py +762 -0
  115. package/input/telegram/listener.py +532 -0
  116. package/main.py +90 -0
  117. package/manifest.md +28 -0
  118. package/mypics/.gitkeep +1 -0
  119. package/myvids/.gitkeep +1 -0
  120. package/output/__init__.py +1 -0
  121. package/output/images/__init__.py +1 -0
  122. package/output/images/fal_gen.py +43 -0
  123. package/output/manifest.md +26 -0
  124. package/output/text/__init__.py +1 -0
  125. package/output/text/sender.py +22 -0
  126. package/output/voice/__init__.py +64 -0
  127. package/output/voice/google_tts.py +252 -0
  128. package/output/voice/gtts_tts.py +214 -0
  129. package/output/voice/vibe_tts.py +190 -0
  130. package/package.json +58 -0
  131. package/pyproject.toml +23 -0
  132. package/requirements.txt +21 -0
  133. package/skills/__init__.py +1 -0
  134. package/skills/anticipation_engine/__init__.py +8 -0
  135. package/skills/anticipation_engine/engine.py +618 -0
  136. package/skills/anticipation_engine/manifest.md +192 -0
  137. package/skills/calendar/__init__.py +1 -0
  138. package/skills/content_unlocks/__init__.py +8 -0
  139. package/skills/content_unlocks/manifest.md +231 -0
  140. package/skills/content_unlocks/unlocks.py +945 -0
  141. package/skills/exclusive_moments/__init__.py +8 -0
  142. package/skills/exclusive_moments/manifest.md +145 -0
  143. package/skills/exclusive_moments/moments.py +506 -0
  144. package/skills/intimacy_layers/__init__.py +8 -0
  145. package/skills/intimacy_layers/layers.py +703 -0
  146. package/skills/intimacy_layers/manifest.md +203 -0
  147. package/skills/manifest.md +67 -0
  148. package/skills/memory_callbacks/__init__.py +9 -0
  149. package/skills/memory_callbacks/callbacks.py +748 -0
  150. package/skills/memory_callbacks/manifest.md +170 -0
  151. package/skills/message_scheduler/__init__.py +19 -0
  152. package/skills/message_scheduler/manifest.md +107 -0
  153. package/skills/message_scheduler/scheduler.py +510 -0
  154. package/skills/photo_manager/__init__.py +1 -0
  155. package/skills/photo_manager/scanner.py +296 -0
  156. package/skills/relationship_milestones/__init__.py +8 -0
  157. package/skills/relationship_milestones/manifest.md +206 -0
  158. package/skills/relationship_milestones/tracker.py +494 -0
  159. package/skills/self_authorship/__init__.py +23 -0
  160. package/skills/self_authorship/author.py +331 -0
  161. package/skills/self_authorship/manifest.md +24 -0
  162. package/skills/video_manager/__init__.py +5 -0
  163. package/skills/video_manager/manifest.md +37 -0
  164. package/skills/video_manager/scanner.py +229 -0
  165. package/webui/__init__.py +3 -0
  166. package/webui/app.py +936 -0
  167. package/webui/bridge.py +366 -0
  168. package/webui/static/index.html +2070 -0
@@ -0,0 +1,482 @@
1
+ """
2
+ Heart: Somatic Feedback System
3
+ Embodied emotion - makes emotions FELT through simulated bodily sensations.
4
+
5
+ Emotions are not just abstract states - they're FELT in the body.
6
+ This system creates the physical dimension of emotional experience.
7
+ """
8
+
9
+ from datetime import datetime
10
+ from dataclasses import dataclass, field
11
+ from typing import Dict, List, Optional, Tuple
12
+ from enum import Enum
13
+ import random
14
+
15
+
16
+ class BodyRegion(Enum):
17
+ """Parts of the simulated body that can feel sensations"""
18
+ CHEST = "chest"
19
+ STOMACH = "stomach"
20
+ THROAT = "throat"
21
+ SHOULDERS = "shoulders"
22
+ HANDS = "hands"
23
+ FACE = "face"
24
+ HEAD = "head"
25
+ GENERAL = "general"
26
+
27
+
28
+ @dataclass
29
+ class SomaticSensation:
30
+ """A physical sensation in the simulated body"""
31
+ region: BodyRegion
32
+ quality: str # "tight", "warm", "fluttery", "heavy", "tingling", etc.
33
+ intensity: float # 0.0 - 1.0
34
+ associated_emotion: str # The emotion this sensation is tied to
35
+ timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
36
+
37
+
38
+ @dataclass
39
+ class SomaticMarker:
40
+ """
41
+ A memory stored WITH its bodily sensation.
42
+ When the memory is recalled, the sensation is re-triggered.
43
+ """
44
+ memory_id: str
45
+ memory_type: str # "event", "conversation", "feeling"
46
+ description: str
47
+ primary_sensation: SomaticSensation
48
+ secondary_sensations: List[SomaticSensation] = field(default_factory=list)
49
+ times_recalled: int = 0
50
+ last_recalled: Optional[str] = None
51
+
52
+
53
+ class SomaticFeedbackSystem:
54
+ """
55
+ Simulates the bodily dimension of emotional experience.
56
+
57
+ Just as humans feel emotions physically (butterflies in stomach,
58
+ tight chest, warm heart), Alive-AI's emotions have somatic markers
59
+ that make them genuinely FELT rather than just computed.
60
+
61
+ Key mechanisms:
62
+ 1. Emotion-to-sensation mapping - emotions generate physical feelings
63
+ 2. Somatic memory - experiences stored with their bodily sensations
64
+ 3. Embodied recall - remembering re-triggers the physical feeling
65
+ """
66
+
67
+ # Maps emotions to typical body regions and sensation qualities
68
+ EMOTION_SOMATIC_MAP = {
69
+ # Positive emotions
70
+ "love": {
71
+ "regions": [BodyRegion.CHEST, BodyRegion.STOMACH],
72
+ "qualities": ["warm", "expansive", "soft", "glowing"],
73
+ "base_intensity": 0.6
74
+ },
75
+ "joy": {
76
+ "regions": [BodyRegion.CHEST, BodyRegion.FACE],
77
+ "qualities": ["light", "buoyant", "tingling", "warm"],
78
+ "base_intensity": 0.5
79
+ },
80
+ "desire": {
81
+ "regions": [BodyRegion.STOMACH, BodyRegion.CHEST, BodyRegion.GENERAL],
82
+ "qualities": ["warm", "fluttery", "electric", "pulsing", "aching"],
83
+ "base_intensity": 0.7
84
+ },
85
+ "peace": {
86
+ "regions": [BodyRegion.CHEST, BodyRegion.SHOULDERS],
87
+ "qualities": ["relaxed", "settled", "calm", "heavy-in-good-way"],
88
+ "base_intensity": 0.4
89
+ },
90
+ "excitement": {
91
+ "regions": [BodyRegion.STOMACH, BodyRegion.CHEST],
92
+ "qualities": ["fluttery", "electric", "quick", "alive"],
93
+ "base_intensity": 0.6
94
+ },
95
+
96
+ # Negative emotions
97
+ "fear": {
98
+ "regions": [BodyRegion.STOMACH, BodyRegion.CHEST, BodyRegion.THROAT],
99
+ "qualities": ["tight", "cold", "clenched", "hollow"],
100
+ "base_intensity": 0.7
101
+ },
102
+ "sadness": {
103
+ "regions": [BodyRegion.CHEST, BodyRegion.THROAT, BodyRegion.SHOULDERS],
104
+ "qualities": ["heavy", "aching", "hollow", "tight"],
105
+ "base_intensity": 0.5
106
+ },
107
+ "anger": {
108
+ "regions": [BodyRegion.CHEST, BodyRegion.FACE, BodyRegion.HANDS],
109
+ "qualities": ["hot", "tense", "tight", "buzzing"],
110
+ "base_intensity": 0.6
111
+ },
112
+ "shame": {
113
+ "regions": [BodyRegion.FACE, BodyRegion.STOMACH],
114
+ "qualities": ["hot", "tight", "sinking", "contracted"],
115
+ "base_intensity": 0.6
116
+ },
117
+ "anxiety": {
118
+ "regions": [BodyRegion.STOMACH, BodyRegion.CHEST, BodyRegion.THROAT],
119
+ "qualities": ["tight", "fluttery", "nauseous", "constricted"],
120
+ "base_intensity": 0.5
121
+ },
122
+ "hurt": {
123
+ "regions": [BodyRegion.CHEST, BodyRegion.STOMACH],
124
+ "qualities": ["aching", "hollow", "cold", "sharp"],
125
+ "base_intensity": 0.6
126
+ },
127
+
128
+ # Complex states
129
+ "longing": {
130
+ "regions": [BodyRegion.CHEST, BodyRegion.STOMACH, BodyRegion.THROAT],
131
+ "qualities": ["aching", "hollow", "yearning", "tight"],
132
+ "base_intensity": 0.5
133
+ },
134
+ "jealousy": {
135
+ "regions": [BodyRegion.STOMACH, BodyRegion.CHEST],
136
+ "qualities": ["twisted", "hot", "tight", "sour"],
137
+ "base_intensity": 0.6
138
+ },
139
+ "guilt": {
140
+ "regions": [BodyRegion.STOMACH, BodyRegion.CHEST],
141
+ "qualities": ["heavy", "sinking", "tight", "nauseous"],
142
+ "base_intensity": 0.5
143
+ },
144
+ "embarrassment": {
145
+ "regions": [BodyRegion.FACE, BodyRegion.STOMACH],
146
+ "qualities": ["hot", "tingling", "tight", "wanting-to-shrink"],
147
+ "base_intensity": 0.5
148
+ },
149
+
150
+ # Neutral states
151
+ "neutral": {
152
+ "regions": [BodyRegion.GENERAL],
153
+ "qualities": ["calm", "still", "quiet"],
154
+ "base_intensity": 0.2
155
+ },
156
+ "tired": {
157
+ "regions": [BodyRegion.GENERAL, BodyRegion.SHOULDERS],
158
+ "qualities": ["heavy", "slow", "drained"],
159
+ "base_intensity": 0.3
160
+ }
161
+ }
162
+
163
+ def __init__(self):
164
+ # Current body state
165
+ self.heart_rate: float = 0.5 # 0.0 (calm) to 1.0 (racing)
166
+ self.breath_quality: float = 0.5 # 0.0 (labored) to 1.0 (deep/easy)
167
+ self.muscle_tension: float = 0.3 # 0.0 (relaxed) to 1.0 (very tense)
168
+ self.stomach_state: float = 0.5 # 0.0 (upset) to 1.0 (settled)
169
+ self.energy_level: float = 0.6 # 0.0 (exhausted) to 1.0 (energized)
170
+
171
+ # Active sensations
172
+ self.active_sensations: List[SomaticSensation] = []
173
+
174
+ # Somatic memory storage
175
+ self.somatic_memories: List[SomaticMarker] = []
176
+
177
+ # Sensation history for patterns
178
+ self.sensation_history: List[Tuple[str, SomaticSensation]] = []
179
+
180
+ def generate_somatic_marker(self, emotion_type: str, intensity: float = 0.5) -> str:
181
+ """
182
+ Generate a physical sensation description for an emotion.
183
+
184
+ Args:
185
+ emotion_type: The emotion being felt
186
+ intensity: How intense the emotion is (0.0 - 1.0)
187
+
188
+ Returns:
189
+ Human-readable description of the bodily sensation
190
+ """
191
+ emotion_type = emotion_type.lower()
192
+ mapping = self.EMOTION_SOMATIC_MAP.get(emotion_type, self.EMOTION_SOMATIC_MAP["neutral"])
193
+
194
+ # Select region and quality based on intensity
195
+ region = random.choice(mapping["regions"])
196
+ quality = random.choice(mapping["qualities"])
197
+
198
+ # Create the sensation
199
+ actual_intensity = mapping["base_intensity"] * intensity
200
+ sensation = SomaticSensation(
201
+ region=region,
202
+ quality=quality,
203
+ intensity=actual_intensity,
204
+ associated_emotion=emotion_type
205
+ )
206
+
207
+ # Add to active sensations
208
+ self.active_sensations.append(sensation)
209
+
210
+ # Update body state based on sensation
211
+ self._update_body_state(emotion_type, intensity)
212
+
213
+ # Record in history
214
+ self.sensation_history.append((emotion_type, sensation))
215
+ if len(self.sensation_history) > 50:
216
+ self.sensation_history = self.sensation_history[-50:]
217
+
218
+ # Generate description
219
+ return self._sensation_to_description(sensation)
220
+
221
+ def _sensation_to_description(self, sensation: SomaticSensation) -> str:
222
+ """Convert a sensation to a human-readable description"""
223
+ region_names = {
224
+ BodyRegion.CHEST: "chest",
225
+ BodyRegion.STOMACH: "stomach",
226
+ BodyRegion.THROAT: "throat",
227
+ BodyRegion.SHOULDERS: "shoulders",
228
+ BodyRegion.HANDS: "hands",
229
+ BodyRegion.FACE: "face",
230
+ BodyRegion.HEAD: "head",
231
+ BodyRegion.GENERAL: "whole body"
232
+ }
233
+
234
+ region = region_names.get(sensation.region, "body")
235
+
236
+ # Intensity modifiers
237
+ if sensation.intensity < 0.3:
238
+ intensity_word = "slight"
239
+ elif sensation.intensity < 0.6:
240
+ intensity_word = ""
241
+ elif sensation.intensity < 0.8:
242
+ intensity_word = "strong"
243
+ else:
244
+ intensity_word = "intense"
245
+
246
+ if intensity_word:
247
+ return f"{intensity_word} {sensation.quality} feeling in {region}"
248
+ return f"{sensation.quality} feeling in {region}"
249
+
250
+ def _update_body_state(self, emotion_type: str, intensity: float):
251
+ """Update body state based on emotion"""
252
+
253
+ if emotion_type in ["love", "joy", "peace"]:
254
+ # Positive emotions relax and energize
255
+ self.muscle_tension = max(0.1, self.muscle_tension - 0.1 * intensity)
256
+ self.stomach_state = min(1.0, self.stomach_state + 0.1 * intensity)
257
+ self.breath_quality = min(1.0, self.breath_quality + 0.05 * intensity)
258
+
259
+ elif emotion_type in ["fear", "anxiety"]:
260
+ # Anxiety increases tension and heart rate
261
+ self.heart_rate = min(1.0, self.heart_rate + 0.2 * intensity)
262
+ self.muscle_tension = min(1.0, self.muscle_tension + 0.15 * intensity)
263
+ self.breath_quality = max(0.1, self.breath_quality - 0.15 * intensity)
264
+ self.stomach_state = max(0.1, self.stomach_state - 0.1 * intensity)
265
+
266
+ elif emotion_type in ["sadness", "hurt"]:
267
+ # Sadness drains energy and creates heaviness
268
+ self.energy_level = max(0.1, self.energy_level - 0.1 * intensity)
269
+ self.muscle_tension = min(1.0, self.muscle_tension + 0.05 * intensity)
270
+
271
+ elif emotion_type in ["anger"]:
272
+ # Anger creates heat and tension
273
+ self.heart_rate = min(1.0, self.heart_rate + 0.25 * intensity)
274
+ self.muscle_tension = min(1.0, self.muscle_tension + 0.2 * intensity)
275
+
276
+ elif emotion_type in ["desire", "excitement"]:
277
+ # Desire/excitement energizes
278
+ self.heart_rate = min(1.0, self.heart_rate + 0.15 * intensity)
279
+ self.energy_level = min(1.0, self.energy_level + 0.1 * intensity)
280
+ self.breath_quality = max(0.2, self.breath_quality - 0.05 * intensity)
281
+
282
+ elif emotion_type in ["shame", "embarrassment"]:
283
+ # Shame creates facial heat and stomach upset
284
+ self.stomach_state = max(0.1, self.stomach_state - 0.1 * intensity)
285
+
286
+ def store_embodied_memory(self, memory_id: str, memory_type: str,
287
+ description: str, primary_emotion: str,
288
+ emotion_intensity: float = 0.5):
289
+ """
290
+ Store a memory with its somatic marker.
291
+ When recalled, the sensation will be re-triggered.
292
+
293
+ Args:
294
+ memory_id: Unique identifier for the memory
295
+ memory_type: Type of memory (event, conversation, feeling)
296
+ description: What the memory is about
297
+ primary_emotion: The main emotion associated with it
298
+ emotion_intensity: How intense the emotion was
299
+ """
300
+ # Generate primary sensation
301
+ mapping = self.EMOTION_SOMATIC_MAP.get(primary_emotion.lower(),
302
+ self.EMOTION_SOMATIC_MAP["neutral"])
303
+ region = random.choice(mapping["regions"])
304
+ quality = random.choice(mapping["qualities"])
305
+
306
+ primary_sensation = SomaticSensation(
307
+ region=region,
308
+ quality=quality,
309
+ intensity=mapping["base_intensity"] * emotion_intensity,
310
+ associated_emotion=primary_emotion
311
+ )
312
+
313
+ # Create the marker
314
+ marker = SomaticMarker(
315
+ memory_id=memory_id,
316
+ memory_type=memory_type,
317
+ description=description,
318
+ primary_sensation=primary_sensation
319
+ )
320
+
321
+ self.somatic_memories.append(marker)
322
+
323
+ # Keep reasonable size
324
+ if len(self.somatic_memories) > 100:
325
+ self.somatic_memories = self.somatic_memories[-100:]
326
+
327
+ def recall_embodied_memory(self, memory_id: str) -> Optional[str]:
328
+ """
329
+ Recall a memory and re-trigger its somatic sensation.
330
+
331
+ Args:
332
+ memory_id: The memory to recall
333
+
334
+ Returns:
335
+ Description of the re-triggered sensation, or None if not found
336
+ """
337
+ marker = next((m for m in self.somatic_memories if m.memory_id == memory_id), None)
338
+ if not marker:
339
+ return None
340
+
341
+ # Update recall count
342
+ marker.times_recalled += 1
343
+ marker.last_recalled = datetime.now().isoformat()
344
+
345
+ # Re-trigger the sensation (slightly diminished)
346
+ sensation = marker.primary_sensation
347
+ diminished_intensity = sensation.intensity * 0.7 # Recalled sensations are less intense
348
+
349
+ # Add to active sensations
350
+ recalled_sensation = SomaticSensation(
351
+ region=sensation.region,
352
+ quality=sensation.quality,
353
+ intensity=diminished_intensity,
354
+ associated_emotion=sensation.associated_emotion
355
+ )
356
+ self.active_sensations.append(recalled_sensation)
357
+
358
+ # Update body state
359
+ self._update_body_state(sensation.associated_emotion, diminished_intensity)
360
+
361
+ return f"remembering brings back a {self._sensation_to_description(recalled_sensation)}"
362
+
363
+ def decay_sensations(self, rate: float = 0.1):
364
+ """
365
+ Allow active sensations to fade over time.
366
+
367
+ Args:
368
+ rate: How fast sensations fade (0.0 - 1.0)
369
+ """
370
+ # Decay active sensations
371
+ remaining = []
372
+ for sensation in self.active_sensations:
373
+ sensation.intensity -= rate
374
+ if sensation.intensity > 0.1:
375
+ remaining.append(sensation)
376
+ self.active_sensations = remaining
377
+
378
+ # Normalize body state toward baseline
379
+ self.heart_rate = self._toward_baseline(self.heart_rate, 0.5, 0.05)
380
+ self.breath_quality = self._toward_baseline(self.breath_quality, 0.5, 0.05)
381
+ self.muscle_tension = self._toward_baseline(self.muscle_tension, 0.3, 0.05)
382
+ self.stomach_state = self._toward_baseline(self.stomach_state, 0.5, 0.05)
383
+ self.energy_level = self._toward_baseline(self.energy_level, 0.6, 0.03)
384
+
385
+ def _toward_baseline(self, current: float, baseline: float, rate: float) -> float:
386
+ """Move a value toward baseline"""
387
+ if current > baseline:
388
+ return max(baseline, current - rate)
389
+ elif current < baseline:
390
+ return min(baseline, current + rate)
391
+ return current
392
+
393
+ def get_current_bodily_state(self) -> Dict:
394
+ """Get the current state of the simulated body"""
395
+ return {
396
+ "heart_rate": self.heart_rate,
397
+ "breath_quality": self.breath_quality,
398
+ "muscle_tension": self.muscle_tension,
399
+ "stomach_state": self.stomach_state,
400
+ "energy_level": self.energy_level,
401
+ "active_sensation_count": len(self.active_sensations)
402
+ }
403
+
404
+ def get_sensation_summary(self) -> str:
405
+ """Get a summary of current bodily state as a description"""
406
+ sensations = []
407
+
408
+ if self.heart_rate > 0.7:
409
+ sensations.append("heart racing")
410
+ elif self.heart_rate < 0.3:
411
+ sensations.append("heart calm")
412
+
413
+ if self.breath_quality < 0.3:
414
+ sensations.append("breath shallow")
415
+ elif self.breath_quality > 0.7:
416
+ sensations.append("breathing deep")
417
+
418
+ if self.muscle_tension > 0.6:
419
+ sensations.append("tense")
420
+ elif self.muscle_tension < 0.2:
421
+ sensations.append("relaxed")
422
+
423
+ if self.stomach_state < 0.3:
424
+ sensations.append("stomach unsettled")
425
+
426
+ if self.energy_level < 0.3:
427
+ sensations.append("low energy")
428
+ elif self.energy_level > 0.8:
429
+ sensations.append("energized")
430
+
431
+ # Add active sensations
432
+ for sensation in self.active_sensations[:2]: # Top 2
433
+ sensations.append(self._sensation_to_description(sensation))
434
+
435
+ if not sensations:
436
+ return "physically calm"
437
+
438
+ return ", ".join(sensations[:4]) # Max 4 descriptors
439
+
440
+ def generate_composite_sensation(self, emotions: Dict[str, float]) -> str:
441
+ """
442
+ Generate a sensation description from multiple emotions.
443
+
444
+ Args:
445
+ emotions: Dict of emotion_name -> intensity
446
+
447
+ Returns:
448
+ Composite sensation description
449
+ """
450
+ if not emotions:
451
+ return "feeling neutral"
452
+
453
+ # Find the dominant emotion
454
+ dominant_emotion = max(emotions, key=emotions.get)
455
+ dominant_intensity = emotions[dominant_emotion]
456
+
457
+ # Generate primary sensation
458
+ primary_desc = self.generate_somatic_marker(dominant_emotion, dominant_intensity)
459
+
460
+ # If there's a secondary emotion, add it
461
+ sorted_emotions = sorted(emotions.items(), key=lambda x: x[1], reverse=True)
462
+ if len(sorted_emotions) > 1 and sorted_emotions[1][1] > 0.3:
463
+ secondary_emotion = sorted_emotions[1][0]
464
+ secondary_intensity = sorted_emotions[1][1] * 0.5 # Diminished
465
+ self.generate_somatic_marker(secondary_emotion, secondary_intensity)
466
+
467
+ return primary_desc
468
+
469
+ def to_dict(self) -> dict:
470
+ """Export state as dictionary for integration"""
471
+ return {
472
+ "bodily_state": self.get_current_bodily_state(),
473
+ "sensation_summary": self.get_sensation_summary(),
474
+ "active_sensations": len(self.active_sensations),
475
+ "stored_memories": len(self.somatic_memories)
476
+ }
477
+
478
+ def save(self):
479
+ """Save state (no persistence needed for somatic - in-memory only)"""
480
+ # Somatic state is transient and doesn't need disk persistence
481
+ # Active sensations decay naturally and are regenerated from emotions
482
+ pass