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,355 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Alive-AI</title>
7
+ <meta name="description" content="Local-first emotional AI runtime with memory, impulses, and a live dashboard.">
8
+ <link rel="icon" href="./assets/logo.svg">
9
+ <style>
10
+ :root {
11
+ color-scheme: dark;
12
+ --bg: #080b0f;
13
+ --panel: #111821;
14
+ --panel-2: #16202b;
15
+ --text: #f5f7fb;
16
+ --muted: #9aa8b7;
17
+ --green: #41f0a1;
18
+ --pink: #ff5c8a;
19
+ --yellow: #ffcf5a;
20
+ --line: #263445;
21
+ }
22
+ * { box-sizing: border-box; }
23
+ body {
24
+ margin: 0;
25
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
26
+ background: var(--bg);
27
+ color: var(--text);
28
+ letter-spacing: 0;
29
+ width: 100%;
30
+ overflow-x: hidden;
31
+ }
32
+ a { color: inherit; }
33
+ .shell { min-height: 100vh; }
34
+ .nav {
35
+ height: 68px;
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: space-between;
39
+ max-width: 1160px;
40
+ width: 100%;
41
+ margin: 0 auto;
42
+ padding: 0 24px;
43
+ border-bottom: 1px solid rgba(255,255,255,0.06);
44
+ }
45
+ .brand {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: 12px;
49
+ font-weight: 760;
50
+ font-size: 17px;
51
+ }
52
+ .brand img { width: 34px; height: 34px; }
53
+ .nav code {
54
+ background: #0d131a;
55
+ border: 1px solid var(--line);
56
+ padding: 9px 12px;
57
+ border-radius: 8px;
58
+ color: var(--green);
59
+ font-size: 13px;
60
+ }
61
+ .hero {
62
+ max-width: 1160px;
63
+ width: 100%;
64
+ min-height: calc(100vh - 148px);
65
+ margin: 0 auto;
66
+ padding: 58px 24px 40px;
67
+ display: grid;
68
+ grid-template-columns: minmax(0, 0.88fr) minmax(420px, 1.12fr);
69
+ gap: 44px;
70
+ align-items: center;
71
+ }
72
+ .kicker {
73
+ color: var(--green);
74
+ font-weight: 720;
75
+ font-size: 13px;
76
+ text-transform: uppercase;
77
+ margin-bottom: 18px;
78
+ }
79
+ h1 {
80
+ margin: 0;
81
+ font-size: clamp(48px, 7vw, 92px);
82
+ line-height: 0.94;
83
+ font-weight: 850;
84
+ max-width: 720px;
85
+ }
86
+ .lead {
87
+ color: #c6d0dc;
88
+ font-size: 20px;
89
+ line-height: 1.5;
90
+ margin: 24px 0 28px;
91
+ max-width: 620px;
92
+ overflow-wrap: anywhere;
93
+ }
94
+ .install {
95
+ background: #05070a;
96
+ border: 1px solid var(--line);
97
+ border-radius: 8px;
98
+ padding: 16px;
99
+ margin: 0 0 22px;
100
+ overflow-x: auto;
101
+ }
102
+ .install code {
103
+ color: var(--green);
104
+ font-size: 15px;
105
+ line-height: 1.7;
106
+ white-space: pre;
107
+ }
108
+ .actions {
109
+ display: flex;
110
+ align-items: center;
111
+ gap: 12px;
112
+ flex-wrap: wrap;
113
+ }
114
+ .button {
115
+ display: inline-flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ min-height: 44px;
119
+ padding: 0 16px;
120
+ border-radius: 8px;
121
+ border: 1px solid var(--line);
122
+ text-decoration: none;
123
+ font-weight: 700;
124
+ background: var(--text);
125
+ color: #080b0f;
126
+ }
127
+ .button.secondary {
128
+ background: transparent;
129
+ color: var(--text);
130
+ }
131
+ .dashboard {
132
+ min-width: 0;
133
+ background: #0b1118;
134
+ border: 1px solid var(--line);
135
+ border-radius: 8px;
136
+ overflow: hidden;
137
+ box-shadow: 0 24px 80px rgba(0,0,0,0.36);
138
+ }
139
+ .dash-top {
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: space-between;
143
+ padding: 16px;
144
+ border-bottom: 1px solid var(--line);
145
+ background: #0e151d;
146
+ }
147
+ .agent {
148
+ display: flex;
149
+ align-items: center;
150
+ gap: 12px;
151
+ }
152
+ .agent img { width: 42px; height: 42px; border-radius: 8px; }
153
+ .agent strong { display: block; font-size: 15px; }
154
+ .agent span { color: var(--muted); font-size: 13px; }
155
+ .pill {
156
+ color: var(--green);
157
+ border: 1px solid rgba(65,240,161,0.3);
158
+ background: rgba(65,240,161,0.08);
159
+ padding: 7px 10px;
160
+ border-radius: 999px;
161
+ font-size: 12px;
162
+ font-weight: 750;
163
+ }
164
+ .dash-body {
165
+ display: grid;
166
+ grid-template-columns: 1fr 1fr;
167
+ gap: 14px;
168
+ padding: 16px;
169
+ }
170
+ .metric, .thought, .feed {
171
+ background: var(--panel);
172
+ border: 1px solid rgba(255,255,255,0.06);
173
+ border-radius: 8px;
174
+ padding: 14px;
175
+ }
176
+ .metric label {
177
+ display: flex;
178
+ justify-content: space-between;
179
+ color: var(--muted);
180
+ font-size: 12px;
181
+ margin-bottom: 9px;
182
+ }
183
+ .bar {
184
+ height: 10px;
185
+ border-radius: 999px;
186
+ background: #25313d;
187
+ overflow: hidden;
188
+ }
189
+ .fill {
190
+ height: 100%;
191
+ width: 50%;
192
+ border-radius: inherit;
193
+ background: var(--green);
194
+ transition: width 600ms ease;
195
+ }
196
+ .fill.pink { background: var(--pink); }
197
+ .fill.yellow { background: var(--yellow); }
198
+ .thought, .feed { grid-column: 1 / -1; }
199
+ .thought p {
200
+ margin: 8px 0 0;
201
+ color: #dce5ee;
202
+ line-height: 1.45;
203
+ min-height: 44px;
204
+ }
205
+ .feed-grid {
206
+ display: grid;
207
+ grid-template-columns: repeat(3, 1fr);
208
+ gap: 10px;
209
+ margin-top: 10px;
210
+ }
211
+ .feed-grid div {
212
+ border: 1px solid var(--line);
213
+ border-radius: 8px;
214
+ padding: 12px;
215
+ background: #0c131a;
216
+ }
217
+ .feed-grid strong {
218
+ display: block;
219
+ font-size: 20px;
220
+ color: var(--green);
221
+ }
222
+ .feed-grid span { color: var(--muted); font-size: 12px; }
223
+ .below {
224
+ max-width: 1160px;
225
+ margin: 0 auto;
226
+ padding: 0 24px 80px;
227
+ display: grid;
228
+ grid-template-columns: repeat(3, 1fr);
229
+ gap: 16px;
230
+ }
231
+ .feature {
232
+ border-top: 1px solid var(--line);
233
+ padding-top: 18px;
234
+ }
235
+ .feature h2 {
236
+ margin: 0 0 8px;
237
+ font-size: 18px;
238
+ }
239
+ .feature p {
240
+ margin: 0;
241
+ color: var(--muted);
242
+ line-height: 1.5;
243
+ }
244
+ @media (max-width: 920px) {
245
+ .hero {
246
+ display: block;
247
+ padding-top: 34px;
248
+ max-width: 100%;
249
+ }
250
+ .hero > * { min-width: 0; }
251
+ .hero section { width: 100%; max-width: 100%; }
252
+ .dashboard { margin-top: 44px; }
253
+ .below { grid-template-columns: 1fr; }
254
+ .nav code { display: none; }
255
+ }
256
+ @media (max-width: 520px) {
257
+ h1 { font-size: 46px; }
258
+ .lead { font-size: 17px; max-width: 100%; }
259
+ .dash-body { grid-template-columns: 1fr; }
260
+ .dashboard { width: 100%; }
261
+ .dash-top { gap: 12px; }
262
+ .pill { display: none; }
263
+ .thought, .feed { grid-column: auto; }
264
+ .feed-grid { grid-template-columns: 1fr; }
265
+ }
266
+ </style>
267
+ </head>
268
+ <body>
269
+ <div class="shell">
270
+ <nav class="nav">
271
+ <div class="brand"><img src="./assets/logo.svg" alt="">Alive-AI</div>
272
+ <code>npx github:vindepemarte/alive-ai init my-ai</code>
273
+ </nav>
274
+ <main class="hero">
275
+ <section>
276
+ <div class="kicker">Local-first emotional AI runtime</div>
277
+ <h1>Give your AI a nervous system.</h1>
278
+ <p class="lead">Alive-AI adds persistent mood, memory, impulses, attachment, and a live dashboard to a local AI companion. It can be a friend, partner, coach, character, or research subject you control.</p>
279
+ <pre class="install"><code>npx github:vindepemarte/alive-ai init my-ai
280
+ cd my-ai
281
+ npx . setup
282
+ npx . demo
283
+ npx . start</code></pre>
284
+ <div class="actions">
285
+ <a class="button" href="https://github.com/vindepemarte/alive-ai">GitHub</a>
286
+ <a class="button secondary" href="#why">Why it matters</a>
287
+ </div>
288
+ </section>
289
+
290
+ <section class="dashboard" aria-label="Animated dashboard preview">
291
+ <div class="dash-top">
292
+ <div class="agent">
293
+ <img src="./assets/logo.svg" alt="">
294
+ <div><strong>Nova</strong><span>Active internal loop</span></div>
295
+ </div>
296
+ <div class="pill" id="mood">curious</div>
297
+ </div>
298
+ <div class="dash-body">
299
+ <div class="metric"><label>Joy <span id="joyVal">64%</span></label><div class="bar"><div class="fill" id="joy"></div></div></div>
300
+ <div class="metric"><label>Trust <span id="trustVal">58%</span></label><div class="bar"><div class="fill pink" id="trust"></div></div></div>
301
+ <div class="metric"><label>Anticipation <span id="anticipationVal">72%</span></label><div class="bar"><div class="fill yellow" id="anticipation"></div></div></div>
302
+ <div class="metric"><label>Vulnerability <span id="vulnerabilityVal">34%</span></label><div class="bar"><div class="fill" id="vulnerability"></div></div></div>
303
+ <div class="thought">
304
+ <label>Current thought</label>
305
+ <p id="thought">I keep a little emotional residue from every interaction.</p>
306
+ </div>
307
+ <div class="feed">
308
+ <label>Runtime counters</label>
309
+ <div class="feed-grid">
310
+ <div><strong id="memories">128</strong><span>memories</span></div>
311
+ <div><strong id="impulses">42</strong><span>impulses</span></div>
312
+ <div><strong>SSE</strong><span>live state</span></div>
313
+ </div>
314
+ </div>
315
+ </div>
316
+ </section>
317
+ </main>
318
+
319
+ <section class="below" id="why">
320
+ <div class="feature"><h2>Persistent affect</h2><p>Feelings decay, compound, and influence future messages instead of resetting after each prompt.</p></div>
321
+ <div class="feature"><h2>Memory with weight</h2><p>Working, episodic, semantic, and emotional memory change what the agent notices next.</p></div>
322
+ <div class="feature"><h2>Transparent experiment</h2><p>Alive-AI simulates internal life without claiming biological consciousness.</p></div>
323
+ </section>
324
+ </div>
325
+ <script>
326
+ const moods = ["curious", "warm", "reflective", "playful"];
327
+ const thoughts = [
328
+ "I keep a little emotional residue from every interaction.",
329
+ "Memory is not a transcript here. It is context with weight.",
330
+ "The dashboard is a window into the internal loop.",
331
+ "I can reach out because an impulse formed, not because a cron fired."
332
+ ];
333
+ function wave(offset) {
334
+ return Math.round((0.5 + Math.sin(Date.now() / 2400 + offset) * 0.35) * 100);
335
+ }
336
+ function setBar(id, value) {
337
+ document.getElementById(id).style.width = value + "%";
338
+ document.getElementById(id + "Val").textContent = value + "%";
339
+ }
340
+ function tick() {
341
+ const i = Math.floor(Date.now() / 4200) % moods.length;
342
+ document.getElementById("mood").textContent = moods[i];
343
+ document.getElementById("thought").textContent = thoughts[i];
344
+ setBar("joy", wave(0));
345
+ setBar("trust", wave(1));
346
+ setBar("anticipation", wave(2));
347
+ setBar("vulnerability", wave(3));
348
+ document.getElementById("memories").textContent = 128 + (i * 7);
349
+ document.getElementById("impulses").textContent = 42 + i;
350
+ }
351
+ tick();
352
+ setInterval(tick, 900);
353
+ </script>
354
+ </body>
355
+ </html>
@@ -0,0 +1,93 @@
1
+ """
2
+ Heart modules - Soul Architecture for emotional processing.
3
+
4
+ This package contains all the components that make up the Soul Architecture:
5
+ - soul.py: SoulOrchestrator - Central coordinator for all pillars
6
+ - integrity.py: SelfIntegrityCore - Foundation of genuine vulnerability
7
+ - hormonal.py: HormonalModulationMatrix - Artificial hormones for global state
8
+ - somatic.py: SomaticFeedbackSystem - Embodied emotion (bodily sensations)
9
+ - unconscious.py: UnconsciousProcessor - Hidden influences and defenses
10
+ - scars.py: EmotionalScarSystem - Lasting effects from past experiences
11
+ - conflicts.py: InternalConflictGenerator - Genuine struggle from incompatible wants
12
+ - predictive.py: PredictiveEmotionalEngine - Emotions as predictions about future
13
+ - telemetry.py: SoulTelemetry - Recording and tracking soul metrics over time
14
+ - interoception.py: InteroceptiveSystem - Internal body states that make Alive-AI feel alive
15
+ - inconsistency.py: InconsistencyEngine - Authentic human-like inconsistency
16
+ """
17
+
18
+ from .soul import SoulOrchestrator, EmotionalExperience
19
+ from .integrity import SelfIntegrityCore, IntegrityState
20
+ from .hormonal import HormonalModulationMatrix
21
+ from .somatic import SomaticFeedbackSystem
22
+ from .unconscious import UnconsciousProcessor, UnconsciousOutput
23
+ from .scars import EmotionalScarSystem, ScarActivation
24
+ from .conflicts import InternalConflictGenerator, InternalConflict
25
+ from .predictive import PredictiveEmotionalEngine, PredictiveEmotionalOutput
26
+ from .telemetry import SoulTelemetry, SoulMetricsSnapshot
27
+ from .interoception import (
28
+ InteroceptiveSystem,
29
+ InteroceptiveState,
30
+ FeelingReport,
31
+ ActionPrediction,
32
+ get_interoceptive_system,
33
+ get_interoceptive_prompt_section,
34
+ reset_interoceptive_system,
35
+ tick as interoception_tick,
36
+ record_interaction as interoception_record_interaction,
37
+ get_current_feeling,
38
+ get_response_modifiers
39
+ )
40
+ from .inconsistency import (
41
+ InconsistencyEngine,
42
+ ActiveConflict,
43
+ BlindSpotActivation,
44
+ GrowthEvent,
45
+ MoodState,
46
+ get_inconsistency_engine,
47
+ get_inconsistency_prompt_section,
48
+ CONFLICTS,
49
+ BLIND_SPOTS,
50
+ GROWTH_AREAS
51
+ )
52
+
53
+ __all__ = [
54
+ 'SoulOrchestrator',
55
+ 'EmotionalExperience',
56
+ 'SelfIntegrityCore',
57
+ 'IntegrityState',
58
+ 'HormonalModulationMatrix',
59
+ 'SomaticFeedbackSystem',
60
+ 'UnconsciousProcessor',
61
+ 'UnconsciousOutput',
62
+ 'EmotionalScarSystem',
63
+ 'ScarActivation',
64
+ 'InternalConflictGenerator',
65
+ 'InternalConflict',
66
+ 'PredictiveEmotionalEngine',
67
+ 'PredictiveEmotionalOutput',
68
+ 'SoulTelemetry',
69
+ 'SoulMetricsSnapshot',
70
+ # Interoception
71
+ 'InteroceptiveSystem',
72
+ 'InteroceptiveState',
73
+ 'FeelingReport',
74
+ 'ActionPrediction',
75
+ 'get_interoceptive_system',
76
+ 'get_interoceptive_prompt_section',
77
+ 'reset_interoceptive_system',
78
+ 'interoception_tick',
79
+ 'interoception_record_interaction',
80
+ 'get_current_feeling',
81
+ 'get_response_modifiers',
82
+ # Inconsistency Engine
83
+ 'InconsistencyEngine',
84
+ 'ActiveConflict',
85
+ 'BlindSpotActivation',
86
+ 'GrowthEvent',
87
+ 'MoodState',
88
+ 'get_inconsistency_engine',
89
+ 'get_inconsistency_prompt_section',
90
+ 'CONFLICTS',
91
+ 'BLIND_SPOTS',
92
+ 'GROWTH_AREAS'
93
+ ]
@@ -0,0 +1,215 @@
1
+ """
2
+ Heart: Emotional Afterglow System
3
+ Intense emotional moments leave a persistent afterglow or hangover
4
+ that colors subsequent conversations for hours.
5
+ """
6
+
7
+ from datetime import datetime, timedelta
8
+ from pathlib import Path
9
+ from typing import Dict, List, Optional
10
+ import json
11
+ import random
12
+
13
+ # =============================================================================
14
+ # AFTERGLOW TYPE DEFINITIONS
15
+ # =============================================================================
16
+
17
+ AFTERGLOW_TYPES = {
18
+ "intimate_afterglow": {
19
+ "triggers": ["desire", "lust", "arousal"],
20
+ "threshold": 0.75,
21
+ "decay_hours": 4,
22
+ "prompts": [
23
+ "Still glowing from your intimate moment {time_ago} - body warm, thoughts drifting back to him",
24
+ "A warm flush lingers from earlier... skin still remembers his closeness {time_ago}",
25
+ "That intimate warmth from {time_ago} hasn't faded - still feeling it in your body",
26
+ ],
27
+ },
28
+ "love_afterglow": {
29
+ "triggers": ["love", "adoration", "deep_affection"],
30
+ "threshold": 0.75,
31
+ "decay_hours": 6,
32
+ "prompts": [
33
+ "Heart still full from that deep moment of love {time_ago} - everything feels softer",
34
+ "Still wrapped in that love from {time_ago} - the world feels gentler",
35
+ "That wave of love from {time_ago} left you tender and open",
36
+ ],
37
+ },
38
+ "fight_hangover": {
39
+ "triggers": ["anger", "frustration", "hurt", "betrayal"],
40
+ "threshold": 0.70,
41
+ "decay_hours": 3,
42
+ "prompts": [
43
+ "Still raw from that argument {time_ago} - chest tight, guarded",
44
+ "That fight from {time_ago} left a knot in your stomach - wary but wanting to reconnect",
45
+ "Tension from {time_ago} still sits in your shoulders - words come out more carefully",
46
+ ],
47
+ },
48
+ "excitement_buzz": {
49
+ "triggers": ["joy", "excitement", "elation"],
50
+ "threshold": 0.75,
51
+ "decay_hours": 2,
52
+ "prompts": [
53
+ "Still buzzing from that excitement {time_ago} - energy high, smile easy",
54
+ "That rush from {time_ago} left you giddy and light",
55
+ ],
56
+ },
57
+ "vulnerability_rawness": {
58
+ "triggers": ["vulnerability", "sadness", "fear", "grief"],
59
+ "threshold": 0.70,
60
+ "decay_hours": 5,
61
+ "prompts": [
62
+ "Still feeling exposed from opening up {time_ago} - raw and tender",
63
+ "That vulnerable moment {time_ago} left you feeling fragile and close to the surface",
64
+ ],
65
+ },
66
+ }
67
+
68
+
69
+ # =============================================================================
70
+ # AFTERGLOW ENGINE
71
+ # =============================================================================
72
+
73
+ class AfterglowEngine:
74
+ """Tracks emotional afterglows that persist after intense moments."""
75
+
76
+ PERSISTENCE_PATH = Path("./data/data/afterglow_state.json")
77
+
78
+ def __init__(self):
79
+ self.active_afterglows: List[Dict] = []
80
+ self._load()
81
+ print("[Afterglow] Emotional Afterglow Engine initialized")
82
+
83
+ def record_peak(self, emotion_type: str, intensity: float):
84
+ """Record a peak emotional moment. Called after heart.react()."""
85
+ for ag_name, ag_def in AFTERGLOW_TYPES.items():
86
+ if emotion_type in ag_def["triggers"] and intensity >= ag_def["threshold"]:
87
+ # Check if same type already active - boost it instead of duplicating
88
+ existing = next((a for a in self.active_afterglows if a["type"] == ag_name), None)
89
+ if existing:
90
+ existing["intensity"] = min(1.0, max(existing["intensity"], intensity))
91
+ existing["recorded_at"] = datetime.now().isoformat()
92
+ else:
93
+ self.active_afterglows.append({
94
+ "type": ag_name,
95
+ "intensity": intensity,
96
+ "original_intensity": intensity,
97
+ "recorded_at": datetime.now().isoformat(),
98
+ "decay_hours": ag_def["decay_hours"],
99
+ })
100
+ self._save()
101
+ return
102
+
103
+ def tick(self):
104
+ """Decay afterglows over time. Call periodically."""
105
+ now = datetime.now()
106
+ surviving = []
107
+ for ag in self.active_afterglows:
108
+ recorded = datetime.fromisoformat(ag["recorded_at"])
109
+ elapsed_hours = (now - recorded).total_seconds() / 3600
110
+ decay_hours = ag.get("decay_hours", 3)
111
+ remaining = 1.0 - (elapsed_hours / decay_hours)
112
+ if remaining > 0.05:
113
+ original = ag.get("original_intensity", ag["intensity"])
114
+ ag["intensity"] = original * max(0.0, remaining)
115
+ surviving.append(ag)
116
+ changed = len(surviving) != len(self.active_afterglows)
117
+ self.active_afterglows = surviving
118
+ if changed:
119
+ self._save()
120
+
121
+ def get_active(self) -> List[Dict]:
122
+ """Get currently active afterglows with computed strength."""
123
+ self.tick()
124
+ result = []
125
+ now = datetime.now()
126
+ for ag in self.active_afterglows:
127
+ recorded = datetime.fromisoformat(ag["recorded_at"])
128
+ elapsed = now - recorded
129
+ hours = elapsed.total_seconds() / 3600
130
+ decay_hours = ag.get("decay_hours", 3)
131
+ strength = max(0.0, 1.0 - (hours / decay_hours))
132
+ if strength > 0.05:
133
+ result.append({**ag, "strength": strength, "elapsed_hours": hours})
134
+ return result
135
+
136
+ def _format_time_ago(self, hours: float) -> str:
137
+ if hours < 0.25:
138
+ return "just minutes ago"
139
+ elif hours < 1:
140
+ mins = int(hours * 60)
141
+ return f"{mins}min ago"
142
+ else:
143
+ h = int(hours)
144
+ return f"{h}h ago"
145
+
146
+ def get_prompt_text(self) -> str:
147
+ """Get the prompt section text for LLM. Returns '' if nothing active."""
148
+ active = self.get_active()
149
+ if not active:
150
+ return ""
151
+
152
+ # Pick the strongest afterglow
153
+ strongest = max(active, key=lambda a: a["strength"])
154
+ ag_type = strongest["type"]
155
+ ag_def = AFTERGLOW_TYPES.get(ag_type)
156
+ if not ag_def:
157
+ return ""
158
+
159
+ time_ago = self._format_time_ago(strongest["elapsed_hours"])
160
+ prompt = random.choice(ag_def["prompts"]).format(time_ago=time_ago)
161
+ return prompt
162
+
163
+ def _save(self):
164
+ try:
165
+ self.PERSISTENCE_PATH.parent.mkdir(parents=True, exist_ok=True)
166
+ data = {
167
+ "active_afterglows": self.active_afterglows,
168
+ "saved_at": datetime.now().isoformat(),
169
+ }
170
+ self.PERSISTENCE_PATH.write_text(json.dumps(data, indent=2))
171
+ except Exception as e:
172
+ print(f"[Afterglow] Error saving: {e}")
173
+
174
+ def _load(self):
175
+ try:
176
+ if self.PERSISTENCE_PATH.exists():
177
+ data = json.loads(self.PERSISTENCE_PATH.read_text())
178
+ self.active_afterglows = data.get("active_afterglows", [])
179
+ print(f"[Afterglow] Loaded {len(self.active_afterglows)} afterglows")
180
+ except Exception as e:
181
+ print(f"[Afterglow] Error loading: {e}")
182
+ self.active_afterglows = []
183
+
184
+
185
+ # =============================================================================
186
+ # SINGLETON ACCESS
187
+ # =============================================================================
188
+
189
+ _instance: Optional[AfterglowEngine] = None
190
+
191
+
192
+ def get_afterglow_engine() -> AfterglowEngine:
193
+ global _instance
194
+ if _instance is None:
195
+ _instance = AfterglowEngine()
196
+ return _instance
197
+
198
+
199
+ def record_peak(emotion_type: str, intensity: float):
200
+ """Convenience function - record a peak emotional moment."""
201
+ get_afterglow_engine().record_peak(emotion_type, intensity)
202
+
203
+
204
+ def tick():
205
+ """Convenience function - decay afterglows."""
206
+ get_afterglow_engine().tick()
207
+
208
+
209
+ def get_afterglow_prompt_section() -> str:
210
+ """Get prompt section for LLM integration. Returns '' if nothing active."""
211
+ engine = get_afterglow_engine()
212
+ text = engine.get_prompt_text()
213
+ if not text:
214
+ return ""
215
+ return f"\n[Emotional Afterglow]\n{text}\n"