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,398 @@
1
+ """Brain: Subconscious Loop — continuous background process"""
2
+ import asyncio, random
3
+ from typing import Callable, Optional
4
+ from .impulses import Impulse, ImpulseType
5
+ from .impulse_generator import ImpulseGenerator
6
+ from .working_memory import WorkingMemory
7
+ from .evaluation import Evaluator
8
+ from .actions import ActionHandler
9
+ from .learning_system import LearningSystem
10
+ from .goal_system import GoalSystem
11
+ from .relationship_memory import RelationshipMemory
12
+ from .relationship import MilestoneType
13
+
14
+
15
+ class SubconsciousLoop:
16
+ EVAL_INTERVAL = 30
17
+ MIN_ACTION_INTERVAL = 7200 # 2 hours minimum between proactive messages (was 30 min)
18
+
19
+ def __init__(self, nervous, heart, llm=None, fast_llm=None, on_impulse: Callable = None, bot_id: str = "alive_ai"):
20
+ self.nervous, self.heart = nervous, heart
21
+ self.llm = llm # Store LLM reference for scheduled message generation
22
+ self.bot_id = bot_id.lower()
23
+ self.impulse_gen = ImpulseGenerator()
24
+ self.working_memory = WorkingMemory()
25
+ self.learning = LearningSystem()
26
+ self.goals = GoalSystem()
27
+ self.relationship = RelationshipMemory()
28
+ self.evaluator = Evaluator(heart, self.impulse_gen, self.learning, self.goals, self.relationship)
29
+ self.action_handler = ActionHandler(nervous, llm, fast_llm, on_impulse)
30
+ self.running, self._task, self.total_evaluations = False, None, 0
31
+
32
+ # Proactive message generator (lazy loaded with LLM)
33
+ self._proactive_generator = None
34
+
35
+ # Message scheduler (for scheduled messages at specific times)
36
+ self._message_scheduler = None
37
+
38
+ async def start(self):
39
+ if self.running: return
40
+ self.running = True
41
+ self._task = asyncio.create_task(self._loop())
42
+
43
+ async def stop(self):
44
+ self.running = False
45
+ if self._task:
46
+ self._task.cancel()
47
+ try: await self._task
48
+ except asyncio.CancelledError: pass
49
+
50
+ def register_interaction(self):
51
+ self.evaluator.register_interaction()
52
+ self.relationship.record_message_received()
53
+
54
+ def init_proactive_generator(self, llm=None, data_path=None):
55
+ """Initialize proactive generator with LLM for contextual messages"""
56
+ try:
57
+ from core.proactive_generator import ProactiveGenerator
58
+ self._proactive_generator = ProactiveGenerator(self.nervous, llm=llm, bot_id=self.bot_id, data_path=data_path)
59
+ print("[Subconscious] Proactive generator initialized")
60
+ except Exception as e:
61
+ print(f"[Subconscious] Failed to init proactive generator: {e}")
62
+
63
+ def init_message_scheduler(self):
64
+ """Initialize message scheduler for scheduled messages"""
65
+ try:
66
+ from skills.message_scheduler import get_message_scheduler
67
+ self._message_scheduler = get_message_scheduler(nervous=self.nervous)
68
+ pending = len(self._message_scheduler.get_pending())
69
+ print(f"[Subconscious] Message scheduler initialized ({pending} pending)")
70
+ except Exception as e:
71
+ print(f"[Subconscious] Failed to init message scheduler: {e}")
72
+
73
+ def record_outcome(self, message, message_type, response_sentiment=0.0, response_type="neutral"):
74
+ self.learning.record_interaction(message=message, message_type=message_type,
75
+ response_sentiment=response_sentiment, response_type=response_type)
76
+ self.relationship.record_message_sent()
77
+ active = self.goals.get_active_goal()
78
+ if active and response_sentiment > 0.3:
79
+ self.goals.record_progress(active.type)
80
+
81
+ def record_milestone(self, mtype: str, desc: str):
82
+ try: self.relationship.record_milestone(MilestoneType(mtype), desc)
83
+ except ValueError: pass
84
+
85
+ def record_experience(self, summary, sentiment=0.5, tags=None):
86
+ self.relationship.record_experience(summary, sentiment, tags)
87
+
88
+ async def _loop(self):
89
+ print("[Subconscious] Loop started - running every 30s")
90
+ while self.running:
91
+ try:
92
+ await self._evaluate()
93
+ self.total_evaluations += 1
94
+ if self.total_evaluations % 5 == 0:
95
+ print(f"[Subconscious] Evaluation #{self.total_evaluations} | Silence: {self.evaluator.get_silence_duration():.0f}min")
96
+ except Exception as e:
97
+ print(f"[Subconscious] Error: {e}")
98
+ await asyncio.sleep(self.EVAL_INTERVAL)
99
+
100
+ async def _evaluate(self):
101
+ impulse = await self.evaluator.evaluate(self.working_memory)
102
+
103
+ # Check for scheduled messages FIRST (highest priority)
104
+ scheduled = self._check_scheduled_messages()
105
+
106
+ # Check for follow-up (unanswered questions / silence)
107
+ follow_up = self._check_follow_up()
108
+
109
+ # Handle scheduled messages with highest priority
110
+ if scheduled:
111
+ await self._handle_scheduled_messages(scheduled)
112
+ return # Don't process other impulses after sending scheduled message
113
+
114
+ if impulse:
115
+ print(f"[Subconscious] Impulse: {impulse.type.value} | strength: {impulse.strength:.2f} | can_act: {self._can_act()}")
116
+ if follow_up:
117
+ print(f"[Subconscious] Follow-up needed: {follow_up['type']} | silence: {follow_up['silence_minutes']:.0f}min")
118
+
119
+ # Prioritize follow-ups over regular impulses.
120
+ if follow_up and self._can_act():
121
+ await self._handle_follow_up(follow_up)
122
+ elif impulse and impulse.should_act and self._can_act():
123
+ print(f"[Subconscious] ACTING on impulse: {impulse.action_hint}")
124
+ await self.action_handler.act_on_impulse(impulse, self.working_memory)
125
+ elif random.random() < 0.1:
126
+ thought_data = await self.evaluator.generate_background_thought(self.working_memory)
127
+ if thought_data:
128
+ # Emit thought to nervous system so WebUI can see it
129
+ await self.nervous.emit("subconscious_thought", thought_data)
130
+
131
+ def _check_follow_up(self):
132
+ """Check if we need to follow up on unanswered question or silence"""
133
+ try:
134
+ from core.message_handler import get_follow_up_system
135
+ from core.directives import is_owner as check_is_owner
136
+ import os
137
+
138
+ follow_up_sys = get_follow_up_system()
139
+ owner_id = os.environ.get("TELEGRAM_OWNER_ID", "")
140
+ is_owner = bool(owner_id) # If owner is configured, assume we're talking to them
141
+
142
+ return follow_up_sys.should_follow_up(is_owner=is_owner)
143
+ except Exception as e:
144
+ print(f"[Subconscious] Follow-up check error: {e}")
145
+ return None
146
+
147
+ def _check_scheduled_messages(self):
148
+ """Check for scheduled messages that are due to be sent"""
149
+ try:
150
+ # Lazy init scheduler
151
+ if self._message_scheduler is None:
152
+ self.init_message_scheduler()
153
+
154
+ if self._message_scheduler is None:
155
+ return None
156
+
157
+ due_messages = self._message_scheduler.get_due_messages()
158
+
159
+ if due_messages:
160
+ print(f"[Subconscious] Found {len(due_messages)} scheduled message(s) due")
161
+ return due_messages
162
+
163
+ return None
164
+ except Exception as e:
165
+ print(f"[Subconscious] Scheduled message check error: {e}")
166
+ return None
167
+
168
+ async def _handle_scheduled_messages(self, messages: list):
169
+ """Send scheduled messages that are due - generates fresh contextual message"""
170
+ try:
171
+ for msg in messages:
172
+ # Add to working memory
173
+ self.working_memory.add_thought(
174
+ f"Time to send scheduled message to {msg.user_id}: {msg.message[:30]}...",
175
+ thought_type="scheduled",
176
+ emotion={"mood": "remembering"}
177
+ )
178
+
179
+ print(f"[Subconscious] Scheduled message due for {msg.user_id}: {msg.message[:50]}...")
180
+
181
+ # Generate a fresh contextual message inspired by the scheduled one
182
+ final_message = await self._generate_contextual_scheduled_message(msg)
183
+
184
+ print(f"[Subconscious] Generated fresh message: {final_message[:50]}...")
185
+
186
+ # Emit as proactive message
187
+ await self.nervous.emit("proactive_message", {
188
+ "message": final_message,
189
+ "user_id": msg.user_id,
190
+ "chat_id": msg.user_id, # Use user_id as chat_id
191
+ "scheduled": True,
192
+ "original_reminder": msg.message,
193
+ "context": msg.context
194
+ })
195
+
196
+ # Mark as sent
197
+ self._message_scheduler.mark_sent(msg.id)
198
+
199
+ except Exception as e:
200
+ print(f"[Subconscious] Error sending scheduled messages: {e}")
201
+
202
+ async def _generate_contextual_scheduled_message(self, scheduled_msg) -> str:
203
+ """
204
+ Generate a fresh, contextual message inspired by the scheduled reminder.
205
+ The scheduled message serves as a topic/reminder, but the actual message
206
+ is generated fresh based on current context.
207
+ """
208
+ from datetime import datetime
209
+
210
+ original_reminder = scheduled_msg.message
211
+ user_id = scheduled_msg.user_id
212
+ context_info = scheduled_msg.context
213
+
214
+ # Try to get user context
215
+ user_name = "babe"
216
+ try:
217
+ from core.user_tracker import get_user_tracker
218
+ tracker = get_user_tracker()
219
+ user = tracker.get_user(user_id)
220
+ if user and user.pet_name:
221
+ user_name = user.pet_name
222
+ except:
223
+ pass
224
+
225
+ # Try to use proactive generator for contextual message
226
+ if self._proactive_generator:
227
+ try:
228
+ from core.user_tracker import get_user_tracker
229
+ tracker = get_user_tracker()
230
+ user = tracker.get_user(user_id)
231
+
232
+ if user:
233
+ # Generate contextual message
234
+ base_message = await self._proactive_generator.generate_for_user(user, "scheduled")
235
+
236
+ # Enhance with the reminder context
237
+ if base_message and self.llm:
238
+ prompt = f"""You scheduled a reminder to message {user_name}. The reminder was: "{original_reminder}"
239
+
240
+ Now it's time to send the message. Generate a fresh, natural message that:
241
+ - Captures the spirit of what you wanted to say
242
+ - Feels spontaneous and in-the-moment
243
+ - Is short (1-2 sentences max)
244
+ - Doesn't mention "scheduling" or "reminders" - just be natural
245
+ - Only reference what's in the reminder above - don't invent new details
246
+
247
+ Your fresh message:"""
248
+
249
+ response = await self.llm.chat([
250
+ {"role": "system", "content": "You are Alive-AI sending a text message."},
251
+ {"role": "user", "content": prompt}
252
+ ], max_tokens=60, temperature=0.7)
253
+
254
+ if response and response.strip():
255
+ return response.strip().strip('"\'')
256
+
257
+ elif base_message:
258
+ return base_message
259
+ except Exception as e:
260
+ print(f"[Subconscious] Proactive generator error: {e}")
261
+
262
+ # Fallback: Use LLM directly
263
+ if self.llm:
264
+ try:
265
+ prompt = f"""You wanted to message {user_name}. Your reminder was: "{original_reminder}"
266
+
267
+ Generate a fresh, natural text message (1-2 sentences) that captures what you wanted to say but feels spontaneous.
268
+ - Don't mention reminders or scheduling
269
+ - Only reference what's in the reminder above - don't invent new details
270
+
271
+ Your message:"""
272
+
273
+ response = await self.llm.chat([
274
+ {"role": "system", "content": "You are Alive-AI sending a text message."},
275
+ {"role": "user", "content": prompt}
276
+ ], max_tokens=60, temperature=0.7)
277
+
278
+ if response and response.strip():
279
+ return response.strip().strip('"\'')
280
+ except Exception as e:
281
+ print(f"[Subconscious] LLM fallback error: {e}")
282
+
283
+ # Ultimate fallback: use original message
284
+ return original_reminder
285
+
286
+ async def _handle_follow_up(self, follow_up_data: dict):
287
+ """Send a contextual follow-up message"""
288
+ try:
289
+ from core.user_tracker import get_user_tracker
290
+
291
+ follow_up_type = follow_up_data.get("type", "silence")
292
+
293
+ # Get users who might need a follow-up
294
+ tracker = get_user_tracker()
295
+ users_to_message = tracker.get_users_for_follow_up(min_silence_minutes=30, max_silence_minutes=180)
296
+
297
+ if not users_to_message:
298
+ print("[Subconscious] No users to follow up with")
299
+ return
300
+
301
+ # For now, pick the user who's been silent the longest
302
+ user = max(users_to_message, key=lambda u: u.silence_minutes)
303
+
304
+ # Generate contextual message
305
+ message = await self._generate_contextual_message(user, follow_up_type)
306
+
307
+ if follow_up_type == "return_from_away":
308
+ # She's coming back from coffee/shower/etc
309
+ reason = follow_up_data.get("reason", "away")
310
+ away_min = follow_up_data.get("away_minutes", 0)
311
+ self.working_memory.add_thought(
312
+ f"Returning from {reason} after {away_min:.0f}min",
313
+ thought_type="return",
314
+ emotion={"mood": "refreshed"}
315
+ )
316
+ else:
317
+ # Regular silence/question follow-up
318
+ silence_min = user.silence_minutes
319
+ self.working_memory.add_thought(
320
+ f"Following up with {user.user_id} after {silence_min:.0f}min silence",
321
+ thought_type="follow_up",
322
+ emotion={"mood": "curious"}
323
+ )
324
+
325
+ print(f"[Subconscious] Sending follow-up to {user.user_id}: {message[:50]}...")
326
+
327
+ # Emit with user context
328
+ await self.nervous.emit("proactive_message", {
329
+ "message": message,
330
+ "user_id": user.user_id,
331
+ "chat_id": user.chat_id
332
+ })
333
+ except Exception as e:
334
+ print(f"[Subconscious] Follow-up error: {e}")
335
+
336
+ async def _generate_contextual_message(self, user, message_type: str = "silence") -> str:
337
+ """Generate a contextual message for a specific user"""
338
+ # Try proactive generator first (has LLM + context)
339
+ if self._proactive_generator:
340
+ try:
341
+ message = await self._proactive_generator.generate_for_user(user, message_type)
342
+ return message
343
+ except Exception as e:
344
+ print(f"[Subconscious] Proactive generator error: {e}")
345
+
346
+ # Fallback to generic messages
347
+ fallbacks = {
348
+ "silence": ["hey... you there?", "thinking about you", "miss talking to you"],
349
+ "follow_up": ["so about earlier...", "was wondering about something"],
350
+ "return_from_away": ["I'm back! 💕", "back now, missed you"],
351
+ }
352
+ templates = fallbacks.get(message_type, fallbacks["silence"])
353
+ return random.choice(templates)
354
+
355
+ def _can_act(self):
356
+ """Check if we can send a proactive message.
357
+
358
+ Requires:
359
+ 1. Not quiet hours
360
+ 2. Minimum interval since last action (2 hours)
361
+ 3. At least 1 hour since last user message (post-conversation buffer)
362
+ """
363
+ # Check quiet hours
364
+ if not self.evaluator.can_act_now():
365
+ return False
366
+
367
+ # Check minimum interval since last proactive action
368
+ if not self.action_handler.can_act_now(self.working_memory, self.MIN_ACTION_INTERVAL / 60):
369
+ return False
370
+
371
+ # NEW: Post-conversation buffer - don't send proactive for 60 min after user's last message
372
+ silence_minutes = self.evaluator.get_silence_duration()
373
+ if silence_minutes < 60: # 1 hour buffer after conversation ends
374
+ return False
375
+
376
+ return True
377
+
378
+ async def generate_proactive_message(self, impulse: Impulse) -> str:
379
+ return await self.action_handler.generate_proactive_message(impulse, self.working_memory)
380
+
381
+ def get_status(self):
382
+ return {"running": self.running, "evaluations": self.total_evaluations,
383
+ "silence": self.evaluator.get_silence_duration(), "can_act": self._can_act(),
384
+ "memory": self.working_memory.get_state_summary(),
385
+ "learning_rate": self.learning.get_recent_success_rate(),
386
+ "goal": self.goals.daily_focus.value if self.goals.daily_focus else None}
387
+
388
+ def get_state_for_save(self):
389
+ return {"learning": self.learning.to_dict(), "goals": self.goals.to_dict(),
390
+ "relationship": self.relationship.to_dict(), "working_memory": self.working_memory.to_dict()}
391
+
392
+ def load_state(self, data):
393
+ if "learning" in data:
394
+ self.learning = LearningSystem.from_dict(data["learning"]); self.evaluator.learning = self.learning
395
+ if "goals" in data:
396
+ self.goals = GoalSystem.from_dict(data["goals"]); self.evaluator.goals = self.goals
397
+ if "relationship" in data:
398
+ self.relationship = RelationshipMemory.from_dict(data["relationship"]); self.evaluator.relationship = self.relationship
@@ -0,0 +1,32 @@
1
+ # Brain: Subconscious Module
2
+
3
+ The living background process that makes Alive-AI feel alive 24/7.
4
+
5
+ ## Files
6
+ - `loop.py` - SubconsciousLoop (main background process)
7
+ - `impulses.py` - ImpulseGenerator, Impulse, ImpulseType enum
8
+ - `working_memory.py` - WorkingMemory (short-term thought stream)
9
+
10
+ ## Impulse Types
11
+ - `MISS_HIM` - Want to say hi, thinking of him
12
+ - `HIGH_DESIRE` - Intimate desire, want attention
13
+ - `CLINGY` - Need reassurance, attachment
14
+ - `CURIOUS` - Wonder about him, ask questions
15
+ - `PLAYFUL` - Want to tease, be flirty
16
+ - `LOVING` - Want to express love
17
+ - `DREAMY` - Reflect, process feelings
18
+ - `BORED` - Want entertainment
19
+ - `NURTURING` - Want to care for him
20
+
21
+ ## How It Works
22
+ 1. Runs every 30 seconds (EVAL_INTERVAL)
23
+ 2. Evaluates emotional state + silence duration
24
+ 3. Generates impulses based on mood/love/desire
25
+ 4. Strong impulses (>=0.5) can trigger proactive messages
26
+ 5. Quiet hours (1am-7am) reduce activity
27
+
28
+ ## Integration Points
29
+ - Reads from: Heart (emotions), WorkingMemory
30
+ - Uses: fast_llm for message generation
31
+ - Emits: `subconscious_impulse` event
32
+ - Callback: `on_impulse` for proactive actions
@@ -0,0 +1,47 @@
1
+ """
2
+ Brain: Subconscious - Relationship Data Models
3
+ Milestone, MilestoneType, SharedExperience dataclasses
4
+ """
5
+
6
+ from datetime import datetime
7
+ from typing import List
8
+ from dataclasses import dataclass, field
9
+ from enum import Enum
10
+
11
+
12
+ class MilestoneType(Enum):
13
+ """Types of relationship milestones"""
14
+ FIRST_MESSAGE = "first_message"
15
+ FIRST_I_LOVE_YOU = "first_i_love_you"
16
+ FIRST_NIGHT_TOGETHER = "first_night_together"
17
+ FIRST_CONFESSION = "first_confession"
18
+ SPECIAL_MOMENT = "special_moment"
19
+ FUNNY_MOMENT = "funny_moment"
20
+ DEEP_CONVERSATION = "deep_conversation"
21
+ FIRST_NICKNAME = "first_nickname"
22
+
23
+
24
+ @dataclass
25
+ class Milestone:
26
+ """A relationship milestone"""
27
+ type: MilestoneType
28
+ description: str
29
+ timestamp: datetime = field(default_factory=datetime.now)
30
+ emotion: float = 0.5
31
+
32
+ def to_dict(self) -> dict:
33
+ return {"type": self.type.value, "description": self.description,
34
+ "timestamp": self.timestamp.isoformat(), "emotion": self.emotion}
35
+
36
+
37
+ @dataclass
38
+ class SharedExperience:
39
+ """A shared experience or memory"""
40
+ summary: str
41
+ timestamp: datetime = field(default_factory=datetime.now)
42
+ sentiment: float = 0.5
43
+ tags: List[str] = field(default_factory=list)
44
+
45
+ def to_dict(self) -> dict:
46
+ return {"summary": self.summary, "timestamp": self.timestamp.isoformat(),
47
+ "sentiment": self.sentiment, "tags": self.tags}
@@ -0,0 +1,83 @@
1
+ """Brain: Subconscious - Relationship Memory — milestones and experiences"""
2
+ from datetime import datetime, timedelta
3
+ from typing import List, Dict, Optional
4
+ from .relationship import Milestone, MilestoneType, SharedExperience
5
+
6
+
7
+ class RelationshipMemory:
8
+ def __init__(self, max_experiences: int = 100):
9
+ self.milestones: List[Milestone] = []
10
+ self.shared_experiences: List[SharedExperience] = []
11
+ self.max_experiences = max_experiences
12
+ self.favorite_topics: Dict[str, int] = {}
13
+ self.conversation_count: int = 0
14
+ self.relationship_start: Optional[datetime] = None
15
+ self.total_messages_sent: int = 0
16
+ self.total_messages_received: int = 0
17
+
18
+ def record_milestone(self, mtype: MilestoneType, desc: str, emotion: float = 0.5):
19
+ if any(m.type == mtype for m in self.milestones): return
20
+ m = Milestone(type=mtype, description=desc, emotion=emotion)
21
+ self.milestones.append(m)
22
+ if not self.relationship_start: self.relationship_start = m.timestamp
23
+
24
+ def record_experience(self, summary: str, sentiment: float = 0.5, tags: List[str] = None):
25
+ self.shared_experiences.append(SharedExperience(summary=summary, sentiment=sentiment, tags=tags or []))
26
+ for t in (tags or []): self.favorite_topics[t] = self.favorite_topics.get(t, 0) + 1
27
+ if len(self.shared_experiences) > self.max_experiences: self.shared_experiences.pop(0)
28
+
29
+ def record_conversation(self, topics: List[str] = None):
30
+ self.conversation_count += 1
31
+ for t in (topics or []): self.favorite_topics[t] = self.favorite_topics.get(t, 0) + 1
32
+
33
+ def record_message_sent(self): self.total_messages_sent += 1
34
+ def record_message_received(self): self.total_messages_received += 1
35
+
36
+ def get_relationship_duration(self) -> timedelta:
37
+ return datetime.now() - self.relationship_start if self.relationship_start else timedelta(0)
38
+
39
+ def get_relationship_stage(self) -> str:
40
+ d = self.get_relationship_duration().days
41
+ if d < 1: return "just_met"
42
+ if d < 7: return "getting_to_know"
43
+ if d < 30: return "developing"
44
+ if d < 90: return "growing"
45
+ return "established"
46
+
47
+ def get_special_memories(self, limit: int = 5) -> List[str]:
48
+ return [m.description for m in self.milestones if m.emotion > 0.7][-limit:]
49
+
50
+ def get_recent_experiences(self, limit: int = 5) -> List[str]:
51
+ return [e.summary for e in self.shared_experiences[-limit:]]
52
+
53
+ def get_relationship_context(self) -> str:
54
+ stage, days = self.get_relationship_stage(), self.get_relationship_duration().days
55
+ special = self.get_special_memories(2)
56
+ ctx = f"Relationship: {stage} ({days} days)"
57
+ return ctx + f"\nSpecial: {', '.join(special)}" if special else ctx
58
+
59
+ def to_dict(self) -> dict:
60
+ return {"milestones": [m.to_dict() for m in self.milestones],
61
+ "shared_experiences": [e.to_dict() for e in self.shared_experiences[-50:]],
62
+ "favorite_topics": self.favorite_topics, "conversation_count": self.conversation_count,
63
+ "total_messages_sent": self.total_messages_sent,
64
+ "total_messages_received": self.total_messages_received,
65
+ "relationship_start": self.relationship_start.isoformat() if self.relationship_start else None}
66
+
67
+ @classmethod
68
+ def from_dict(cls, data: dict) -> "RelationshipMemory":
69
+ rm = cls()
70
+ for md in data.get("milestones", []):
71
+ rm.milestones.append(Milestone(type=MilestoneType(md["type"]), description=md["description"],
72
+ timestamp=datetime.fromisoformat(md["timestamp"]), emotion=md.get("emotion", 0.5)))
73
+ for ed in data.get("shared_experiences", []):
74
+ rm.shared_experiences.append(SharedExperience(summary=ed["summary"],
75
+ timestamp=datetime.fromisoformat(ed["timestamp"]), sentiment=ed.get("sentiment", 0.5),
76
+ tags=ed.get("tags", [])))
77
+ rm.favorite_topics = data.get("favorite_topics", {})
78
+ rm.conversation_count = data.get("conversation_count", 0)
79
+ rm.total_messages_sent = data.get("total_messages_sent", 0)
80
+ rm.total_messages_received = data.get("total_messages_received", 0)
81
+ if data.get("relationship_start"):
82
+ rm.relationship_start = datetime.fromisoformat(data["relationship_start"])
83
+ return rm
@@ -0,0 +1,74 @@
1
+ """
2
+ Brain: Subconscious - Response Analyzer
3
+ Analyzes user replies to determine sentiment for learning/goal systems
4
+ """
5
+
6
+ _POSITIVE_WORDS = {
7
+ "haha", "lol", "love", "yes", "yeah", "yess", "omg", "amazing", "perfect",
8
+ "cute", "aww", "miss", "want", "need", "beautiful", "gorgeous", "funny",
9
+ "thanks", "thank", "sweet", "babe", "baby", "honey", "darling", "amore",
10
+ "happy", "glad", "great", "awesome", "wow", "nice", "good", "best",
11
+ }
12
+
13
+ _NEGATIVE_WORDS = {
14
+ "stop", "no", "nah", "whatever", "bye", "leave", "annoying", "boring",
15
+ "shut", "enough", "tired", "busy", "later", "not now", "don't",
16
+ }
17
+
18
+ _INTIMATE_WORDS = {
19
+ "feel", "feelings", "love", "heart", "soul", "deep", "connection",
20
+ "trust", "forever", "future", "dream", "afraid", "scared", "honest",
21
+ "vulnerable", "mean to me", "important", "special", "serious",
22
+ }
23
+
24
+
25
+ def analyze_response(text: str) -> dict:
26
+ """Analyze a user's reply — returns sentiment, response_type, and topic signals"""
27
+ if not text:
28
+ return {"sentiment": 0.0, "type": "empty", "is_positive": False,
29
+ "is_intimate": False, "is_dismissal": False}
30
+
31
+ lower = text.lower()
32
+ words = set(lower.split())
33
+
34
+ pos_count = len(words & _POSITIVE_WORDS)
35
+ neg_count = len(words & _NEGATIVE_WORDS)
36
+ intimate_count = len(words & _INTIMATE_WORDS)
37
+
38
+ is_short = len(text) < 10
39
+ is_long = len(text) > 50
40
+ has_question = "?" in text
41
+ has_emoji = any(ord(c) > 127 for c in text)
42
+
43
+ # Score sentiment -1 to 1
44
+ sentiment = 0.0
45
+ sentiment += pos_count * 0.2
46
+ sentiment -= neg_count * 0.25
47
+ if is_long:
48
+ sentiment += 0.15
49
+ if has_question:
50
+ sentiment += 0.1
51
+ if has_emoji:
52
+ sentiment += 0.1
53
+ if is_short and neg_count > 0:
54
+ sentiment -= 0.2
55
+
56
+ sentiment = max(-1.0, min(1.0, sentiment))
57
+
58
+ # Classify response type
59
+ if is_short and neg_count > 0:
60
+ resp_type = "dismissal"
61
+ elif pos_count > 0 or is_long or has_question:
62
+ resp_type = "engaged"
63
+ elif is_short:
64
+ resp_type = "brief"
65
+ else:
66
+ resp_type = "neutral"
67
+
68
+ return {
69
+ "sentiment": sentiment,
70
+ "type": resp_type,
71
+ "is_positive": sentiment > 0.2,
72
+ "is_intimate": intimate_count >= 2,
73
+ "is_dismissal": resp_type == "dismissal",
74
+ }