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,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"
|