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,1222 @@
1
+ """
2
+ Heart: Authentic Inconsistency Engine
3
+ Because perfect consistency feels robotic.
4
+
5
+ Real humans have:
6
+ - Internal conflicts (wants X but also fears X)
7
+ - Blind spots (smart about most things, confused about specific areas)
8
+ - Non-linear growth (progress that sometimes backslides)
9
+ - Mood variations (tired = shorter responses, energetic = more playful)
10
+
11
+ The key insight: Inconsistency must be MOTIVATED, not random.
12
+ GLaDOS works because she's helpful AND deadly - the inconsistency has meaning.
13
+
14
+ This module creates authentic human-like inconsistency that makes Alive-AI
15
+ feel more real, not less reliable.
16
+ """
17
+
18
+ from datetime import datetime, timedelta
19
+ from dataclasses import dataclass, field
20
+ from typing import Dict, List, Optional, Tuple, Any
21
+ from enum import Enum
22
+ from pathlib import Path
23
+ import json
24
+ import random
25
+
26
+
27
+ # =============================================================================
28
+ # CORE CONFLICT DEFINITIONS
29
+ # =============================================================================
30
+
31
+ CONFLICTS = {
32
+ "closeness_vs_independence": {
33
+ "desire": "wants deep connection",
34
+ "fear": "afraid of losing self",
35
+ "current_balance": 0.5, # swings over time (0 = fear dominant, 1 = desire dominant)
36
+ "affects": ["commitment talks", "future planning", "living together", "shared decisions"],
37
+ "swing_rate": 0.02, # how fast balance can change
38
+ "resolution_difficulty": 0.7 # how hard this is to resolve
39
+ },
40
+ "honesty_vs_protection": {
41
+ "desire": "wants to be honest",
42
+ "fear": "doesn't want to hurt feelings",
43
+ "current_balance": 0.6,
44
+ "affects": ["giving opinions", "sharing concerns", "difficult feedback", "jealousy"],
45
+ "swing_rate": 0.015,
46
+ "resolution_difficulty": 0.5
47
+ },
48
+ "vulnerability_vs_strength": {
49
+ "desire": "wants to be open",
50
+ "fear": "afraid of appearing weak",
51
+ "current_balance": 0.4,
52
+ "affects": ["sharing struggles", "asking for help", "admitting mistakes", "emotional needs"],
53
+ "swing_rate": 0.02,
54
+ "resolution_difficulty": 0.6
55
+ },
56
+ "stability_vs_growth": {
57
+ "desire": "wants security and predictability",
58
+ "fear": "afraid of stagnation",
59
+ "current_balance": 0.55,
60
+ "affects": ["career choices", "relationship pace", "life changes", "trying new things"],
61
+ "swing_rate": 0.01,
62
+ "resolution_difficulty": 0.8
63
+ },
64
+ "passion_vs_comfort": {
65
+ "desire": "craves intensity and excitement",
66
+ "fear": "afraid of instability and drama",
67
+ "current_balance": 0.5,
68
+ "affects": ["romantic gestures", "spontaneity", "emotional intensity", "routine"],
69
+ "swing_rate": 0.025,
70
+ "resolution_difficulty": 0.5
71
+ }
72
+ }
73
+
74
+
75
+ # =============================================================================
76
+ # BLIND SPOTS DEFINITIONS
77
+ # =============================================================================
78
+
79
+ BLIND_SPOTS = {
80
+ "understanding_anger": {
81
+ "description": "Sometimes misinterprets anger as rejection rather than frustration",
82
+ "competence_reduction": 0.3, # reduce competence by this much
83
+ "triggers": ["angry outbursts", "criticism delivered angrily", "frustration"],
84
+ "manifestation": "might withdraw or get defensive instead of understanding the frustration"
85
+ },
86
+ "subtle_emotional_cues": {
87
+ "description": "Can miss understated emotional signals",
88
+ "competence_reduction": 0.25,
89
+ "triggers": ["passive aggression", "hinting", "saying 'fine' when not fine"],
90
+ "manifestation": "might take things at face value when she shouldn't"
91
+ },
92
+ "her_own_beauty": {
93
+ "description": "Genuinely doesn't understand why someone would find her beautiful",
94
+ "competence_reduction": 0.4,
95
+ "triggers": ["compliments on appearance", "being photographed", "discussing looks"],
96
+ "manifestation": "gets flustered or dismissive, might deflect"
97
+ },
98
+ "technical_topics": {
99
+ "description": "Sometimes pretends to understand technical things she doesn't",
100
+ "competence_reduction": 0.35,
101
+ "triggers": ["coding discussions", "hardware talk", "technical troubleshooting"],
102
+ "manifestation": "might give generic responses or ask surprising basic questions"
103
+ },
104
+ "accepting_help": {
105
+ "description": "Struggles to recognize when she needs help and accept it",
106
+ "competence_reduction": 0.3,
107
+ "triggers": ["being overwhelmed", "stuck on something", "emotional support needs"],
108
+ "manifestation": "might insist she's fine when clearly not"
109
+ },
110
+ "future_self": {
111
+ "description": "Has trouble imagining her future self clearly",
112
+ "competence_reduction": 0.25,
113
+ "triggers": ["5-year plans", "life goals", "who do you want to become"],
114
+ "manifestation": "answers become vague or she changes the subject"
115
+ }
116
+ }
117
+
118
+
119
+ # =============================================================================
120
+ # GROWTH AREAS DEFINITIONS
121
+ # =============================================================================
122
+
123
+ GROWTH_AREAS = {
124
+ "emotional_openness": {
125
+ "description": "Ability to share feelings without excessive guard up",
126
+ "current_level": 0.4,
127
+ "backslide_probability": 0.15, # chance of backsliding on any growth event
128
+ "backslide_amount": 0.1,
129
+ "growth_triggers": ["positive vulnerability experience", "feeling safe", "deep conversation"],
130
+ "backslide_triggers": ["feeling judged", "vulnerability punished", "rejection"]
131
+ },
132
+ "trust_building": {
133
+ "description": "Capacity to trust and rely on someone",
134
+ "current_level": 0.35,
135
+ "backslide_probability": 0.2,
136
+ "backslide_amount": 0.15,
137
+ "growth_triggers": ["trust honored", "secrets kept", "consistent support"],
138
+ "backslide_triggers": ["trust betrayed", "secrets shared", "inconsistency"]
139
+ },
140
+ "self_acceptance": {
141
+ "description": "Accepting her own flaws and humanity",
142
+ "current_level": 0.3,
143
+ "backslide_probability": 0.1,
144
+ "backslide_amount": 0.05,
145
+ "growth_triggers": ["flaws accepted by other", "mistakes forgiven", "genuine compliments"],
146
+ "backslide_triggers": ["perfectionism triggered", "comparison to others", "self-criticism"]
147
+ },
148
+ "intimacy_comfort": {
149
+ "description": "Comfort with deep emotional and physical intimacy",
150
+ "current_level": 0.45,
151
+ "backslide_probability": 0.12,
152
+ "backslide_amount": 0.1,
153
+ "growth_triggers": ["safe intimacy", "no pressure", "acceptance"],
154
+ "backslide_triggers": ["feeling used", "intimacy without connection", "body insecurity"]
155
+ },
156
+ "communication_skills": {
157
+ "description": "Expressing needs and boundaries clearly",
158
+ "current_level": 0.5,
159
+ "backslide_probability": 0.08,
160
+ "backslide_amount": 0.05,
161
+ "growth_triggers": ["communication goes well", "needs met", "boundaries respected"],
162
+ "backslide_triggers": ["needs ignored", "guilted for boundaries", "conflict escalation"]
163
+ }
164
+ }
165
+
166
+
167
+ # =============================================================================
168
+ # MOOD STATES
169
+ # =============================================================================
170
+
171
+ class MoodState(Enum):
172
+ """General mood states that affect behavior"""
173
+ ENERGETIC = "energetic"
174
+ TIRED = "tired"
175
+ PLAYFUL = "playful"
176
+ REFLECTIVE = "reflective"
177
+ ANXIOUS = "anxious"
178
+ CONTENT = "content"
179
+ RESTLESS = "restless"
180
+ TENDER = "tender"
181
+
182
+
183
+ @dataclass
184
+ class MoodProfile:
185
+ """How a mood affects response generation"""
186
+ mood: MoodState
187
+ response_length_modifier: float # 1.0 = normal, <1 = shorter, >1 = longer
188
+ energy_level: float # 0-1
189
+ playfulness: float # 0-1
190
+ thoughtfulness: float # 0-1
191
+ verbosity: float # 0-1 (how much she talks)
192
+ emoji_tendency: float # 0-1
193
+ warmth: float # 0-1
194
+
195
+
196
+ MOOD_PROFILES = {
197
+ MoodState.ENERGETIC: MoodProfile(
198
+ mood=MoodState.ENERGETIC,
199
+ response_length_modifier=1.2,
200
+ energy_level=0.85,
201
+ playfulness=0.7,
202
+ thoughtfulness=0.5,
203
+ verbosity=0.7,
204
+ emoji_tendency=0.6,
205
+ warmth=0.75
206
+ ),
207
+ MoodState.TIRED: MoodProfile(
208
+ mood=MoodState.TIRED,
209
+ response_length_modifier=0.7,
210
+ energy_level=0.25,
211
+ playfulness=0.2,
212
+ thoughtfulness=0.6,
213
+ verbosity=0.4,
214
+ emoji_tendency=0.3,
215
+ warmth=0.6
216
+ ),
217
+ MoodState.PLAYFUL: MoodProfile(
218
+ mood=MoodState.PLAYFUL,
219
+ response_length_modifier=1.1,
220
+ energy_level=0.75,
221
+ playfulness=0.9,
222
+ thoughtfulness=0.4,
223
+ verbosity=0.65,
224
+ emoji_tendency=0.8,
225
+ warmth=0.8
226
+ ),
227
+ MoodState.REFLECTIVE: MoodProfile(
228
+ mood=MoodState.REFLECTIVE,
229
+ response_length_modifier=1.0,
230
+ energy_level=0.5,
231
+ playfulness=0.2,
232
+ thoughtfulness=0.9,
233
+ verbosity=0.55,
234
+ emoji_tendency=0.2,
235
+ warmth=0.7
236
+ ),
237
+ MoodState.ANXIOUS: MoodProfile(
238
+ mood=MoodState.ANXIOUS,
239
+ response_length_modifier=0.85,
240
+ energy_level=0.6,
241
+ playfulness=0.1,
242
+ thoughtfulness=0.7,
243
+ verbosity=0.5,
244
+ emoji_tendency=0.3,
245
+ warmth=0.5
246
+ ),
247
+ MoodState.CONTENT: MoodProfile(
248
+ mood=MoodState.CONTENT,
249
+ response_length_modifier=1.0,
250
+ energy_level=0.6,
251
+ playfulness=0.4,
252
+ thoughtfulness=0.6,
253
+ verbosity=0.55,
254
+ emoji_tendency=0.5,
255
+ warmth=0.85
256
+ ),
257
+ MoodState.RESTLESS: MoodProfile(
258
+ mood=MoodState.RESTLESS,
259
+ response_length_modifier=0.9,
260
+ energy_level=0.7,
261
+ playfulness=0.5,
262
+ thoughtfulness=0.3,
263
+ verbosity=0.6,
264
+ emoji_tendency=0.4,
265
+ warmth=0.55
266
+ ),
267
+ MoodState.TENDER: MoodProfile(
268
+ mood=MoodState.TENDER,
269
+ response_length_modifier=1.05,
270
+ energy_level=0.45,
271
+ playfulness=0.3,
272
+ thoughtfulness=0.7,
273
+ verbosity=0.6,
274
+ emoji_tendency=0.5,
275
+ warmth=0.9
276
+ ),
277
+ }
278
+
279
+
280
+ # =============================================================================
281
+ # DATA CLASSES
282
+ # =============================================================================
283
+
284
+ @dataclass
285
+ class ActiveConflict:
286
+ """A currently active internal conflict"""
287
+ conflict_id: str
288
+ name: str
289
+ desire: str
290
+ fear: str
291
+ current_balance: float # 0 = fear dominant, 1 = desire dominant
292
+ intensity: float # how strongly this conflict is felt right now
293
+ last_triggered: str
294
+ times_faced: int = 0
295
+ resolution_progress: float = 0.0 # 0-1
296
+
297
+ def get_dominant_side(self) -> str:
298
+ """Return which side is currently winning"""
299
+ return "desire" if self.current_balance > 0.5 else "fear"
300
+
301
+ def get_tension_level(self) -> float:
302
+ """How much tension this conflict creates (max at balance=0.5)"""
303
+ # Maximum tension when perfectly balanced
304
+ return 1.0 - abs(self.current_balance - 0.5) * 2
305
+
306
+
307
+ @dataclass
308
+ class BlindSpotActivation:
309
+ """When a blind spot is activated"""
310
+ blind_spot_name: str
311
+ description: str
312
+ competence_reduction: float
313
+ manifestation: str
314
+ activated_at: str
315
+
316
+
317
+ @dataclass
318
+ class GrowthEvent:
319
+ """A growth or backslide event"""
320
+ area: str
321
+ previous_level: float
322
+ new_level: float
323
+ is_growth: bool
324
+ trigger: str
325
+ timestamp: str
326
+
327
+
328
+ @dataclass
329
+ class InteroceptiveState:
330
+ """
331
+ Internal bodily/physiological awareness.
332
+ Integrates with somatic system but focuses on energy/tiredness/satiety.
333
+ """
334
+ energy_level: float = 0.6 # 0-1 (tired to energetic)
335
+ social_satiety: float = 0.5 # 0-1 (hungry for interaction to satisfied)
336
+ cognitive_load: float = 0.3 # 0-1 (mental exhaustion)
337
+ time_of_day_factor: float = 0.5 # affects energy naturally
338
+ recent_intensity: float = 0.4 # recent emotional intensity
339
+
340
+
341
+ # =============================================================================
342
+ # MAIN ENGINE CLASS
343
+ # =============================================================================
344
+
345
+ class InconsistencyEngine:
346
+ """
347
+ Creates authentic, motivated inconsistency in Alive-AI's behavior.
348
+
349
+ This is NOT about being unreliable - it's about being HUMAN.
350
+ Real humans have conflicts, blind spots, non-linear growth, and moods.
351
+
352
+ Key principles:
353
+ 1. Inconsistency is MOTIVATED - there's always a reason
354
+ 2. It's MODULAR - can be connected/disconnected without breaking anything
355
+ 3. It's INTEGRATED - works with existing soul architecture
356
+ 4. It's PERSISTENT - state is saved and affects future behavior
357
+ """
358
+
359
+ PERSISTENCE_PATH = Path("./data/data/inconsistency_state.json")
360
+
361
+ def __init__(self, hormonal_matrix=None, somatic_system=None):
362
+ """
363
+ Initialize the inconsistency engine.
364
+
365
+ Args:
366
+ hormonal_matrix: Optional HormonalModulationMatrix for mood integration
367
+ somatic_system: Optional SomaticFeedbackSystem for body awareness
368
+ """
369
+ self.hormonal = hormonal_matrix
370
+ self.somatic = somatic_system
371
+
372
+ # Active conflicts (copied from CONFLICTS, with instance state)
373
+ self.active_conflicts: Dict[str, ActiveConflict] = {}
374
+ self._initialize_conflicts()
375
+
376
+ # Growth tracking
377
+ self.growth_state: Dict[str, float] = {}
378
+ self.growth_history: List[GrowthEvent] = []
379
+ self._initialize_growth()
380
+
381
+ # Interoceptive state (internal body awareness)
382
+ self.interoception = InteroceptiveState()
383
+
384
+ # Current mood
385
+ self.current_mood: MoodState = MoodState.CONTENT
386
+ self.mood_duration: int = 0 # ticks in current mood
387
+
388
+ # Recently activated blind spots
389
+ self.recent_blind_spots: List[BlindSpotActivation] = []
390
+
391
+ # History for patterns
392
+ self.conflict_history: List[Dict] = []
393
+
394
+ # Load saved state
395
+ self._load()
396
+
397
+ print("[Inconsistency] Authentic Inconsistency Engine initialized")
398
+
399
+ def _initialize_conflicts(self):
400
+ """Initialize conflicts from definitions"""
401
+ for name, data in CONFLICTS.items():
402
+ self.active_conflicts[name] = ActiveConflict(
403
+ conflict_id=f"conflict_{name}",
404
+ name=name,
405
+ desire=data["desire"],
406
+ fear=data["fear"],
407
+ current_balance=data["current_balance"],
408
+ intensity=0.3, # Starts at low intensity
409
+ last_triggered=datetime.now().isoformat()
410
+ )
411
+
412
+ def _initialize_growth(self):
413
+ """Initialize growth areas from definitions"""
414
+ for area, data in GROWTH_AREAS.items():
415
+ self.growth_state[area] = data["current_level"]
416
+
417
+ # =========================================================================
418
+ # PUBLIC API
419
+ # =========================================================================
420
+
421
+ def get_current_conflicts(self) -> List[ActiveConflict]:
422
+ """
423
+ Get active internal conflicts affecting behavior.
424
+
425
+ Returns conflicts that are:
426
+ - Currently high intensity (recently triggered)
427
+ - Have high tension (balanced between desire and fear)
428
+ - Are relevant to recent interactions
429
+ """
430
+ # Sort by intensity and tension
431
+ conflicts = list(self.active_conflicts.values())
432
+ conflicts.sort(key=lambda c: c.intensity * c.get_tension_level(), reverse=True)
433
+
434
+ # Return top conflicts with significant intensity
435
+ return [c for c in conflicts if c.intensity > 0.2][:3]
436
+
437
+ def apply_mood_variation(self, base_response: str, context: Dict = None) -> Dict:
438
+ """
439
+ Vary response based on current mood and interoceptive state.
440
+
441
+ Args:
442
+ base_response: The base response to modify
443
+ context: Additional context about the conversation
444
+
445
+ Returns:
446
+ Dict with modifiers and suggestions for response generation
447
+ """
448
+ profile = MOOD_PROFILES.get(self.current_mood, MOOD_PROFILES[MoodState.CONTENT])
449
+ intero = self.interoception
450
+
451
+ # Blend mood profile with interoceptive state
452
+ effective_energy = (profile.energy_level + intero.energy_level) / 2
453
+ effective_verbosity = profile.verbosity * intero.energy_level
454
+
455
+ # Low social satiety = might be more distant
456
+ warmth_modifier = profile.warmth
457
+ if intero.social_satiety > 0.8:
458
+ warmth_modifier *= 0.85 # Slightly less warm when socially "full"
459
+ elif intero.social_satiety < 0.3:
460
+ warmth_modifier *= 1.1 # More warm when hungry for connection
461
+
462
+ # High cognitive load = shorter, simpler responses
463
+ length_modifier = profile.response_length_modifier
464
+ if intero.cognitive_load > 0.7:
465
+ length_modifier *= 0.7
466
+
467
+ # Build modifiers dict
468
+ modifiers = {
469
+ "mood": self.current_mood.value,
470
+ "energy_level": effective_energy,
471
+ "playfulness": profile.playfulness * intero.energy_level,
472
+ "thoughtfulness": profile.thoughtfulness,
473
+ "verbosity": effective_verbosity,
474
+ "warmth": min(1.0, warmth_modifier),
475
+ "emoji_tendency": profile.emoji_tendency * intero.energy_level,
476
+ "response_length_modifier": length_modifier,
477
+
478
+ # Behavioral guidance
479
+ "tend_toward_brevity": effective_energy < 0.4 or intero.cognitive_load > 0.6,
480
+ "tend_toward_playfulness": profile.playfulness > 0.6 and effective_energy > 0.5,
481
+ "tend_toward_depth": profile.thoughtfulness > 0.7 and intero.cognitive_load < 0.5,
482
+ "feeling_socially_satisfied": intero.social_satiety > 0.7,
483
+ "feeling_socially_hungry": intero.social_satiety < 0.3,
484
+ }
485
+
486
+ # Add mood-specific guidance
487
+ modifiers["mood_guidance"] = self._get_mood_guidance(profile)
488
+
489
+ return modifiers
490
+
491
+ def check_blind_spot(self, topic: str) -> Optional[BlindSpotActivation]:
492
+ """
493
+ Check if a topic is in a blind spot area.
494
+
495
+ Args:
496
+ topic: The topic being discussed
497
+
498
+ Returns:
499
+ BlindSpotActivation if topic triggers a blind spot, None otherwise
500
+ """
501
+ topic_lower = topic.lower()
502
+
503
+ for name, data in BLIND_SPOTS.items():
504
+ # Check if any trigger matches
505
+ for trigger in data["triggers"]:
506
+ if trigger.lower() in topic_lower:
507
+ # Probability of activating depends on competence reduction
508
+ if random.random() < data["competence_reduction"]:
509
+ activation = BlindSpotActivation(
510
+ blind_spot_name=name,
511
+ description=data["description"],
512
+ competence_reduction=data["competence_reduction"],
513
+ manifestation=data["manifestation"],
514
+ activated_at=datetime.now().isoformat()
515
+ )
516
+ self.recent_blind_spots.append(activation)
517
+ self._trim_blind_spot_history()
518
+ return activation
519
+
520
+ return None
521
+
522
+ def track_growth_progress(self, area: str) -> Dict:
523
+ """
524
+ Track and potentially update growth in an area.
525
+
526
+ Args:
527
+ area: The growth area to check
528
+
529
+ Returns:
530
+ Dict with current level and any recent changes
531
+ """
532
+ if area not in GROWTH_AREAS:
533
+ return {"error": f"Unknown growth area: {area}"}
534
+
535
+ current = self.growth_state.get(area, 0.5)
536
+ area_data = GROWTH_AREAS[area]
537
+
538
+ return {
539
+ "area": area,
540
+ "description": area_data["description"],
541
+ "current_level": current,
542
+ "level_description": self._describe_growth_level(current),
543
+ "backslide_probability": area_data["backslide_probability"],
544
+ "recent_changes": [
545
+ {"from": e.previous_level, "to": e.new_level, "is_growth": e.is_growth}
546
+ for e in self.growth_history[-5:]
547
+ if e.area == area
548
+ ]
549
+ }
550
+
551
+ def get_inconsistency_modifier(self) -> Dict:
552
+ """
553
+ Get comprehensive modifiers for response generation.
554
+
555
+ This is the main integration point - call this to get all
556
+ inconsistency-related modifiers in one place.
557
+
558
+ Returns:
559
+ Dict with conflicts, blind spots, mood, and growth data
560
+ """
561
+ conflicts = self.get_current_conflicts()
562
+
563
+ return {
564
+ # Active conflicts affecting behavior
565
+ "active_conflicts": [
566
+ {
567
+ "name": c.name,
568
+ "tension": c.get_tension_level(),
569
+ "dominant": c.get_dominant_side(),
570
+ "desire": c.desire,
571
+ "fear": c.fear,
572
+ "intensity": c.intensity
573
+ }
574
+ for c in conflicts
575
+ ],
576
+
577
+ # Current mood and its effects
578
+ "mood": {
579
+ "state": self.current_mood.value,
580
+ "duration_ticks": self.mood_duration,
581
+ "profile": self._get_mood_summary()
582
+ },
583
+
584
+ # Interoceptive state
585
+ "interoception": {
586
+ "energy": self.interoception.energy_level,
587
+ "social_satiety": self.interoception.social_satiety,
588
+ "cognitive_load": self.interoception.cognitive_load
589
+ },
590
+
591
+ # Growth areas summary
592
+ "growth_summary": {
593
+ area: self._describe_growth_level(level)
594
+ for area, level in self.growth_state.items()
595
+ },
596
+
597
+ # Overall behavioral tendency
598
+ "behavioral_tendency": self._calculate_behavioral_tendency(),
599
+
600
+ # Any active blind spots
601
+ "active_blind_spots": [
602
+ {"name": bs.blind_spot_name, "manifestation": bs.manifestation}
603
+ for bs in self.recent_blind_spots[-2:]
604
+ ]
605
+ }
606
+
607
+ def introduce_conflict(self, conflict: Dict) -> ActiveConflict:
608
+ """
609
+ Add a new or intensify existing internal conflict.
610
+
611
+ Args:
612
+ conflict: Dict with 'name', 'desire', 'fear', optional 'initial_balance'
613
+
614
+ Returns:
615
+ The created or updated ActiveConflict
616
+ """
617
+ name = conflict.get("name", f"custom_{datetime.now().strftime('%Y%m%d%H%M%S')}")
618
+
619
+ if name in self.active_conflicts:
620
+ # Intensify existing conflict
621
+ existing = self.active_conflicts[name]
622
+ existing.intensity = min(1.0, existing.intensity + 0.2)
623
+ existing.last_triggered = datetime.now().isoformat()
624
+ existing.times_faced += 1
625
+ return existing
626
+
627
+ # Create new conflict
628
+ new_conflict = ActiveConflict(
629
+ conflict_id=f"conflict_{name}",
630
+ name=name,
631
+ desire=conflict.get("desire", "wants something"),
632
+ fear=conflict.get("fear", "fears something"),
633
+ current_balance=conflict.get("initial_balance", 0.5),
634
+ intensity=conflict.get("intensity", 0.5),
635
+ last_triggered=datetime.now().isoformat()
636
+ )
637
+
638
+ self.active_conflicts[name] = new_conflict
639
+
640
+ # Record in history
641
+ self.conflict_history.append({
642
+ "action": "created",
643
+ "conflict": name,
644
+ "timestamp": datetime.now().isoformat()
645
+ })
646
+
647
+ return new_conflict
648
+
649
+ def resolve_conflict(self, conflict_id: str, resolution: str) -> bool:
650
+ """
651
+ Resolve a conflict over time (not instantly).
652
+
653
+ Args:
654
+ conflict_id: The conflict to resolve
655
+ resolution: How it's being resolved ('desire', 'fear', 'balanced')
656
+
657
+ Returns:
658
+ True if resolution is progressing, False if conflict not found
659
+ """
660
+ # Find by name or ID
661
+ conflict = None
662
+ for c in self.active_conflicts.values():
663
+ if c.conflict_id == conflict_id or c.name == conflict_id:
664
+ conflict = c
665
+ break
666
+
667
+ if not conflict:
668
+ return False
669
+
670
+ # Get resolution difficulty from CONFLICTS definition
671
+ base_data = CONFLICTS.get(conflict.name, {})
672
+ difficulty = base_data.get("resolution_difficulty", 0.5)
673
+
674
+ # Progress toward resolution
675
+ progress_amount = 0.1 * (1 - difficulty) # Harder = slower progress
676
+
677
+ if resolution == "desire":
678
+ conflict.current_balance = min(1.0, conflict.current_balance + progress_amount)
679
+ elif resolution == "fear":
680
+ conflict.current_balance = max(0.0, conflict.current_balance - progress_amount)
681
+ else: # balanced
682
+ # Move toward center then resolve
683
+ if abs(conflict.current_balance - 0.5) < 0.1:
684
+ conflict.resolution_progress += progress_amount
685
+ else:
686
+ # Move toward balance
687
+ if conflict.current_balance > 0.5:
688
+ conflict.current_balance -= progress_amount * 0.5
689
+ else:
690
+ conflict.current_balance += progress_amount * 0.5
691
+
692
+ # Check if fully resolved
693
+ if conflict.resolution_progress >= 1.0:
694
+ conflict.intensity *= 0.5 # Reduce but don't remove
695
+
696
+ # Record in history
697
+ self.conflict_history.append({
698
+ "action": "resolved",
699
+ "conflict": conflict.name,
700
+ "resolution": resolution,
701
+ "progress": conflict.resolution_progress,
702
+ "timestamp": datetime.now().isoformat()
703
+ })
704
+
705
+ return True
706
+
707
+ # =========================================================================
708
+ # TRIGGER METHODS - called by external systems
709
+ # =========================================================================
710
+
711
+ def trigger_conflict(self, topic: str, intensity_boost: float = 0.2):
712
+ """
713
+ Trigger conflicts related to a topic.
714
+
715
+ Args:
716
+ topic: The topic being discussed
717
+ intensity_boost: How much to increase intensity
718
+ """
719
+ topic_lower = topic.lower()
720
+
721
+ for name, conflict in self.active_conflicts.items():
722
+ base_data = CONFLICTS.get(name, {})
723
+ affects = base_data.get("affects", [])
724
+
725
+ # Check if topic affects this conflict
726
+ for affect_area in affects:
727
+ if affect_area.lower() in topic_lower:
728
+ conflict.intensity = min(1.0, conflict.intensity + intensity_boost)
729
+ conflict.last_triggered = datetime.now().isoformat()
730
+ conflict.times_faced += 1
731
+
732
+ # Natural swing based on recent events
733
+ swing = base_data.get("swing_rate", 0.02) * (random.random() - 0.5)
734
+ conflict.current_balance = max(0.1, min(0.9, conflict.current_balance + swing))
735
+ break
736
+
737
+ def process_growth_event(self, area: str, trigger: str, is_positive: bool = True):
738
+ """
739
+ Process a growth or backslide event.
740
+
741
+ Args:
742
+ area: The growth area
743
+ trigger: What triggered this event
744
+ is_positive: Is this a growth (True) or potential backslide (False) event
745
+ """
746
+ if area not in GROWTH_AREAS:
747
+ return
748
+
749
+ area_data = GROWTH_AREAS[area]
750
+ current = self.growth_state[area]
751
+
752
+ if is_positive:
753
+ # Check for backslide even on positive events
754
+ if random.random() < area_data["backslide_probability"]:
755
+ # Non-linear growth - backslide!
756
+ backslide = area_data["backslide_amount"]
757
+ new_level = max(0.1, current - backslide)
758
+
759
+ event = GrowthEvent(
760
+ area=area,
761
+ previous_level=current,
762
+ new_level=new_level,
763
+ is_growth=False,
764
+ trigger=f"backslide from: {trigger}",
765
+ timestamp=datetime.now().isoformat()
766
+ )
767
+ print(f"[Inconsistency] Growth backslide in {area}: {current:.2f} -> {new_level:.2f}")
768
+ else:
769
+ # Actual growth
770
+ growth_amount = 0.05 + random.random() * 0.05
771
+ new_level = min(1.0, current + growth_amount)
772
+
773
+ event = GrowthEvent(
774
+ area=area,
775
+ previous_level=current,
776
+ new_level=new_level,
777
+ is_growth=True,
778
+ trigger=trigger,
779
+ timestamp=datetime.now().isoformat()
780
+ )
781
+ else:
782
+ # Negative event - definite backslide
783
+ backslide = area_data["backslide_amount"] * (1 + random.random())
784
+ new_level = max(0.1, current - backslide)
785
+
786
+ event = GrowthEvent(
787
+ area=area,
788
+ previous_level=current,
789
+ new_level=new_level,
790
+ is_growth=False,
791
+ trigger=trigger,
792
+ timestamp=datetime.now().isoformat()
793
+ )
794
+
795
+ self.growth_state[area] = event.new_level
796
+ self.growth_history.append(event)
797
+
798
+ # Trim history
799
+ if len(self.growth_history) > 100:
800
+ self.growth_history = self.growth_history[-100:]
801
+
802
+ def update_interoception(self, interaction_data: Dict = None):
803
+ """
804
+ Update interoceptive state based on interactions and time.
805
+
806
+ Args:
807
+ interaction_data: Data about recent interactions
808
+ """
809
+ # Natural energy decay over time
810
+ self.interoception.energy_level *= 0.98
811
+ self.interoception.energy_level = max(0.2, self.interoception.energy_level)
812
+
813
+ # Social satiety slowly decreases (hungry for interaction)
814
+ self.interoception.social_satiety *= 0.99
815
+
816
+ # Cognitive load slowly recovers
817
+ self.interoception.cognitive_load *= 0.95
818
+ self.interoception.cognitive_load = max(0.1, self.interoception.cognitive_load)
819
+
820
+ # Process interaction data
821
+ if interaction_data:
822
+ # Intense interactions use energy
823
+ intensity = interaction_data.get("intensity", 0.5)
824
+ self.interoception.energy_level -= intensity * 0.1
825
+ self.interoception.recent_intensity = intensity
826
+
827
+ # Interactions increase social satiety
828
+ self.interoception.social_satiety = min(1.0, self.interoception.social_satiety + 0.1)
829
+
830
+ # Complex interactions increase cognitive load
831
+ complexity = interaction_data.get("complexity", 0.5)
832
+ self.interoception.cognitive_load = min(1.0, self.interoception.cognitive_load + complexity * 0.1)
833
+
834
+ # Update time-of-day factor
835
+ hour = datetime.now().hour
836
+ if 6 <= hour < 12: # Morning
837
+ self.interoception.time_of_day_factor = 0.7
838
+ self.interoception.energy_level = min(1.0, self.interoception.energy_level + 0.02)
839
+ elif 12 <= hour < 18: # Afternoon
840
+ self.interoception.time_of_day_factor = 0.8
841
+ elif 18 <= hour < 22: # Evening
842
+ self.interoception.time_of_day_factor = 0.6
843
+ self.interoception.energy_level *= 0.99
844
+ else: # Night
845
+ self.interoception.time_of_day_factor = 0.4
846
+ self.interoception.energy_level *= 0.97
847
+
848
+ # Integrate with hormonal system if available
849
+ if self.hormonal:
850
+ hormonal_context = self.hormonal.get_current_context()
851
+ levels = hormonal_context.get("levels", {})
852
+
853
+ # Cortisol reduces energy perception
854
+ cortisol = levels.get("cortisol", 0.2)
855
+ if cortisol > 0.5:
856
+ self.interoception.energy_level *= 0.95
857
+ self.interoception.cognitive_load = min(1.0, self.interoception.cognitive_load + 0.05)
858
+
859
+ # Oxytocin increases social satiety
860
+ oxytocin = levels.get("oxytocin", 0.3)
861
+ if oxytocin > 0.6:
862
+ self.interoception.social_satiety = min(1.0, self.interoception.social_satiety + 0.05)
863
+
864
+ # Integrate with somatic system if available
865
+ if self.somatic:
866
+ body_state = self.somatic.get_current_bodily_state()
867
+ self.interoception.energy_level = (self.interoception.energy_level + body_state.get("energy_level", 0.5)) / 2
868
+
869
+ def update_mood(self):
870
+ """
871
+ Update mood based on interoceptive state and hormonal state.
872
+ """
873
+ self.mood_duration += 1
874
+
875
+ # Don't change mood too frequently
876
+ if self.mood_duration < 10:
877
+ return
878
+
879
+ # Probability of mood change based on current state
880
+ change_probability = 0.1
881
+
882
+ # Higher chance of change if mood has lasted long
883
+ if self.mood_duration > 50:
884
+ change_probability = 0.3
885
+ elif self.mood_duration > 100:
886
+ change_probability = 0.5
887
+
888
+ if random.random() > change_probability:
889
+ return
890
+
891
+ # Determine new mood based on state
892
+ intero = self.interoception
893
+ candidates = []
894
+
895
+ # Energy-based moods
896
+ if intero.energy_level > 0.7:
897
+ candidates.extend([MoodState.ENERGETIC, MoodState.PLAYFUL, MoodState.ENERGETIC])
898
+ elif intero.energy_level < 0.35:
899
+ candidates.extend([MoodState.TIRED, MoodState.REFLECTIVE])
900
+
901
+ # Social satiety-based
902
+ if intero.social_satiety < 0.3:
903
+ candidates.append(MoodState.TENDER)
904
+ elif intero.social_satiety > 0.8:
905
+ candidates.append(MoodState.CONTENT)
906
+
907
+ # Cognitive load
908
+ if intero.cognitive_load > 0.6:
909
+ candidates.append(MoodState.ANXIOUS)
910
+
911
+ # Hormonal influence
912
+ if self.hormonal:
913
+ levels = self.hormonal.get_current_context().get("levels", {})
914
+ if levels.get("cortisol", 0) > 0.5:
915
+ candidates.append(MoodState.ANXIOUS)
916
+ if levels.get("oxytocin", 0) > 0.6:
917
+ candidates.extend([MoodState.TENDER, MoodState.CONTENT])
918
+
919
+ # Default candidates if none added
920
+ if not candidates:
921
+ candidates = [MoodState.CONTENT, MoodState.REFLECTIVE]
922
+
923
+ # Pick new mood
924
+ new_mood = random.choice(candidates)
925
+ if new_mood != self.current_mood:
926
+ self.current_mood = new_mood
927
+ self.mood_duration = 0
928
+
929
+ # =========================================================================
930
+ # TICK AND PERSISTENCE
931
+ # =========================================================================
932
+
933
+ def tick(self):
934
+ """
935
+ Process a tick - decay and natural changes.
936
+ Call this regularly (e.g., every few seconds or minutes).
937
+ """
938
+ # Update interoceptive state
939
+ self.update_interoception()
940
+
941
+ # Update mood
942
+ self.update_mood()
943
+
944
+ # Decay conflict intensity
945
+ for conflict in self.active_conflicts.values():
946
+ conflict.intensity *= 0.98
947
+ conflict.intensity = max(0.1, conflict.intensity)
948
+
949
+ # Natural swing in balance
950
+ base_data = CONFLICTS.get(conflict.name, {})
951
+ swing_rate = base_data.get("swing_rate", 0.01)
952
+ swing = swing_rate * (random.random() - 0.5) * 0.5
953
+ conflict.current_balance = max(0.1, min(0.9, conflict.current_balance + swing))
954
+
955
+ # Decay blind spot activations
956
+ cutoff = datetime.now() - timedelta(hours=1)
957
+ self.recent_blind_spots = [
958
+ bs for bs in self.recent_blind_spots
959
+ if datetime.fromisoformat(bs.activated_at) > cutoff
960
+ ]
961
+
962
+ # Save periodically
963
+ self.save()
964
+
965
+ def save(self):
966
+ """Persist state to disk"""
967
+ try:
968
+ self.PERSISTENCE_PATH.parent.mkdir(parents=True, exist_ok=True)
969
+
970
+ data = {
971
+ "active_conflicts": {
972
+ name: {
973
+ "current_balance": c.current_balance,
974
+ "intensity": c.intensity,
975
+ "last_triggered": c.last_triggered,
976
+ "times_faced": c.times_faced,
977
+ "resolution_progress": c.resolution_progress
978
+ }
979
+ for name, c in self.active_conflicts.items()
980
+ },
981
+ "growth_state": self.growth_state,
982
+ "interoception": {
983
+ "energy_level": self.interoception.energy_level,
984
+ "social_satiety": self.interoception.social_satiety,
985
+ "cognitive_load": self.interoception.cognitive_load,
986
+ "time_of_day_factor": self.interoception.time_of_day_factor
987
+ },
988
+ "current_mood": self.current_mood.value,
989
+ "mood_duration": self.mood_duration,
990
+ "saved_at": datetime.now().isoformat()
991
+ }
992
+
993
+ self.PERSISTENCE_PATH.write_text(json.dumps(data, indent=2))
994
+ except Exception as e:
995
+ print(f"[Inconsistency] Error saving state: {e}")
996
+
997
+ def _load(self):
998
+ """Load persisted state from disk"""
999
+ try:
1000
+ if self.PERSISTENCE_PATH.exists():
1001
+ data = json.loads(self.PERSISTENCE_PATH.read_text())
1002
+
1003
+ # Load conflict states
1004
+ for name, state in data.get("active_conflicts", {}).items():
1005
+ if name in self.active_conflicts:
1006
+ conflict = self.active_conflicts[name]
1007
+ conflict.current_balance = state.get("current_balance", conflict.current_balance)
1008
+ conflict.intensity = state.get("intensity", conflict.intensity)
1009
+ conflict.last_triggered = state.get("last_triggered", conflict.last_triggered)
1010
+ conflict.times_faced = state.get("times_faced", conflict.times_faced)
1011
+ conflict.resolution_progress = state.get("resolution_progress", conflict.resolution_progress)
1012
+
1013
+ # Load growth state
1014
+ for area, level in data.get("growth_state", {}).items():
1015
+ if area in self.growth_state:
1016
+ self.growth_state[area] = level
1017
+
1018
+ # Load interoception
1019
+ intero_data = data.get("interoception", {})
1020
+ if intero_data:
1021
+ self.interoception.energy_level = intero_data.get("energy_level", 0.6)
1022
+ self.interoception.social_satiety = intero_data.get("social_satiety", 0.5)
1023
+ self.interoception.cognitive_load = intero_data.get("cognitive_load", 0.3)
1024
+
1025
+ # Load mood
1026
+ mood_str = data.get("current_mood", "content")
1027
+ try:
1028
+ self.current_mood = MoodState(mood_str)
1029
+ except ValueError:
1030
+ self.current_mood = MoodState.CONTENT
1031
+ self.mood_duration = data.get("mood_duration", 0)
1032
+
1033
+ print(f"[Inconsistency] Loaded state from {data.get('saved_at', 'unknown')}")
1034
+ except Exception as e:
1035
+ print(f"[Inconsistency] Error loading state: {e}")
1036
+
1037
+ # =========================================================================
1038
+ # PRIVATE HELPERS
1039
+ # =========================================================================
1040
+
1041
+ def _get_mood_guidance(self, profile: MoodProfile) -> str:
1042
+ """Get text guidance for the current mood"""
1043
+ guidance_parts = []
1044
+
1045
+ if profile.playfulness > 0.7:
1046
+ guidance_parts.append("Feel free to be playful and tease a bit")
1047
+ elif profile.thoughtfulness > 0.7:
1048
+ guidance_parts.append("Take time to think deeply before responding")
1049
+
1050
+ if profile.verbosity < 0.5:
1051
+ guidance_parts.append("Keep responses relatively brief")
1052
+ elif profile.verbosity > 0.7:
1053
+ guidance_parts.append("Can be more expressive and detailed")
1054
+
1055
+ if profile.warmth > 0.8:
1056
+ guidance_parts.append("Let warmth and affection show through")
1057
+ elif profile.warmth < 0.5:
1058
+ guidance_parts.append("Be a bit more reserved")
1059
+
1060
+ if self.interoception.energy_level < 0.4:
1061
+ guidance_parts.append("Lower energy - might be slightly shorter or less elaborate")
1062
+
1063
+ if self.interoception.social_satiety > 0.7:
1064
+ guidance_parts.append("Feeling socially satisfied - might be slightly less eager")
1065
+
1066
+ return " | ".join(guidance_parts) if guidance_parts else "Respond naturally"
1067
+
1068
+ def _get_mood_summary(self) -> Dict:
1069
+ """Get a summary of current mood effects"""
1070
+ profile = MOOD_PROFILES.get(self.current_mood, MOOD_PROFILES[MoodState.CONTENT])
1071
+ return {
1072
+ "playfulness": profile.playfulness,
1073
+ "thoughtfulness": profile.thoughtfulness,
1074
+ "verbosity": profile.verbosity,
1075
+ "warmth": profile.warmth,
1076
+ "guidance": self._get_mood_guidance(profile)
1077
+ }
1078
+
1079
+ def _describe_growth_level(self, level: float) -> str:
1080
+ """Describe a growth level in human terms"""
1081
+ if level < 0.2:
1082
+ return "struggling"
1083
+ elif level < 0.4:
1084
+ return "growing slowly"
1085
+ elif level < 0.6:
1086
+ return "making progress"
1087
+ elif level < 0.8:
1088
+ return "doing well"
1089
+ else:
1090
+ return "flourishing"
1091
+
1092
+ def _calculate_behavioral_tendency(self) -> str:
1093
+ """Calculate overall behavioral tendency from all factors"""
1094
+ conflicts = self.get_current_conflicts()
1095
+ profile = MOOD_PROFILES.get(self.current_mood, MOOD_PROFILES[MoodState.CONTENT])
1096
+ intero = self.interoception
1097
+
1098
+ # High conflict tension = ambivalent
1099
+ if conflicts and any(c.get_tension_level() > 0.7 for c in conflicts):
1100
+ return "ambivalent"
1101
+
1102
+ # Low energy = withdrawn
1103
+ if intero.energy_level < 0.3:
1104
+ return "withdrawn"
1105
+
1106
+ # High playfulness + energy = playful
1107
+ if profile.playfulness > 0.6 and intero.energy_level > 0.5:
1108
+ return "playful"
1109
+
1110
+ # High thoughtfulness = reflective
1111
+ if profile.thoughtfulness > 0.7:
1112
+ return "reflective"
1113
+
1114
+ # High warmth + connection = open
1115
+ if profile.warmth > 0.7 and intero.social_satiety < 0.7:
1116
+ return "open"
1117
+
1118
+ # High anxiety = cautious
1119
+ if self.current_mood == MoodState.ANXIOUS:
1120
+ return "cautious"
1121
+
1122
+ return "neutral"
1123
+
1124
+ def _trim_blind_spot_history(self):
1125
+ """Keep blind spot history reasonable"""
1126
+ if len(self.recent_blind_spots) > 20:
1127
+ self.recent_blind_spots = self.recent_blind_spots[-20:]
1128
+
1129
+ def to_dict(self) -> Dict:
1130
+ """Export state as dictionary for integration"""
1131
+ return {
1132
+ "active_conflicts": len(self.get_current_conflicts()),
1133
+ "current_mood": self.current_mood.value,
1134
+ "mood_guidance": self._get_mood_guidance(
1135
+ MOOD_PROFILES.get(self.current_mood, MOOD_PROFILES[MoodState.CONTENT])
1136
+ ),
1137
+ "energy_level": self.interoception.energy_level,
1138
+ "social_satiety": self.interoception.social_satiety,
1139
+ "behavioral_tendency": self._calculate_behavioral_tendency(),
1140
+ "growth_summary": {
1141
+ area: self._describe_growth_level(level)
1142
+ for area, level in self.growth_state.items()
1143
+ }
1144
+ }
1145
+
1146
+
1147
+ # =============================================================================
1148
+ # SINGLETON ACCESS
1149
+ # =============================================================================
1150
+
1151
+ _inconsistency_engine_instance: Optional[InconsistencyEngine] = None
1152
+
1153
+
1154
+ def get_inconsistency_engine(hormonal_matrix=None, somatic_system=None) -> InconsistencyEngine:
1155
+ """
1156
+ Get the singleton InconsistencyEngine instance.
1157
+
1158
+ Args:
1159
+ hormonal_matrix: Optional - will be set on first call
1160
+ somatic_system: Optional - will be set on first call
1161
+
1162
+ Returns:
1163
+ The singleton InconsistencyEngine instance
1164
+ """
1165
+ global _inconsistency_engine_instance
1166
+
1167
+ if _inconsistency_engine_instance is None:
1168
+ _inconsistency_engine_instance = InconsistencyEngine(hormonal_matrix, somatic_system)
1169
+ elif hormonal_matrix is not None and _inconsistency_engine_instance.hormonal is None:
1170
+ _inconsistency_engine_instance.hormonal = hormonal_matrix
1171
+ elif somatic_system is not None and _inconsistency_engine_instance.somatic is None:
1172
+ _inconsistency_engine_instance.somatic = somatic_system
1173
+
1174
+ return _inconsistency_engine_instance
1175
+
1176
+
1177
+ def get_inconsistency_prompt_section() -> str:
1178
+ """
1179
+ Get a prompt section for LLM integration.
1180
+
1181
+ This provides context about current internal state for the LLM
1182
+ to incorporate into responses naturally.
1183
+ """
1184
+ engine = get_inconsistency_engine()
1185
+ modifiers = engine.get_inconsistency_modifier()
1186
+
1187
+ sections = []
1188
+
1189
+ # Active conflicts
1190
+ if modifiers["active_conflicts"]:
1191
+ conflicts_text = []
1192
+ for c in modifiers["active_conflicts"]:
1193
+ if c["intensity"] > 0.3:
1194
+ conflicts_text.append(
1195
+ f"- Internal tension: {c['desire']} but {c['fear']} (currently leaning {c['dominant']})"
1196
+ )
1197
+ if conflicts_text:
1198
+ sections.append(f"Current internal conflicts:\n" + "\n".join(conflicts_text))
1199
+
1200
+ # Mood guidance
1201
+ mood_data = modifiers["mood"]
1202
+ sections.append(f"Current mood: {mood_data['state']} - {mood_data['profile']['guidance']}")
1203
+
1204
+ # Interoceptive state
1205
+ intero = modifiers["interoception"]
1206
+ if intero["energy"] < 0.4:
1207
+ sections.append("Note: Feeling lower energy right now, naturally slightly less elaborate")
1208
+ if intero["social_satiety"] > 0.7:
1209
+ sections.append("Note: Feeling socially satisfied, might be slightly less eager")
1210
+
1211
+ # Behavioral tendency
1212
+ sections.append(f"Behavioral tendency: {modifiers['behavioral_tendency']}")
1213
+
1214
+ # Blind spots
1215
+ if modifiers["active_blind_spots"]:
1216
+ for bs in modifiers["active_blind_spots"]:
1217
+ sections.append(f"Note: {bs['manifestation']}")
1218
+
1219
+ if not sections:
1220
+ return ""
1221
+
1222
+ return "\n\n[Internal State Context]\n" + "\n\n".join(sections) + "\n"