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
package/Dockerfile ADDED
@@ -0,0 +1,24 @@
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ gcc \
8
+ g++ \
9
+ make \
10
+ libportaudio2 \
11
+ libportaudiocpp0 \
12
+ portaudio19-dev \
13
+ ffmpeg \
14
+ && rm -rf /var/lib/apt/lists/*
15
+
16
+ # Install Python dependencies
17
+ COPY requirements.txt .
18
+ RUN pip install --no-cache-dir -r requirements.txt
19
+
20
+ # Copy application
21
+ COPY . .
22
+
23
+ # Run
24
+ CMD ["python", "main.py"]
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vindepemarte
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # Alive-AI
2
+
3
+ ![Alive-AI logo](docs/assets/logo.svg)
4
+
5
+ Give your AI a nervous system: persistent feelings, memory, impulses, and a local dashboard.
6
+
7
+ Most agents answer a prompt and reset. Alive-AI keeps internal state alive between messages. It can be your friend, boyfriend, girlfriend, study partner, creative partner, or any local companion you configure. The experiment asks a harder question: what changes when an AI does not just respond, but carries emotional residue forward?
8
+
9
+ Alive-AI does not claim biological consciousness. It is an open-source runtime for simulated affect: mood, attachment, trust, desire, memory, inconsistency, idle thoughts, and proactive impulses.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npx github:vindepemarte/alive-ai init my-ai
15
+ cd my-ai
16
+ npx . setup
17
+ npx . demo
18
+ ```
19
+
20
+ Start the real runtime:
21
+
22
+ ```bash
23
+ npx . start
24
+ ```
25
+
26
+ The local dashboard runs at:
27
+
28
+ ```text
29
+ http://127.0.0.1:8080
30
+ ```
31
+
32
+ ## Why This Is Different
33
+
34
+ - **Emotions persist.** State does not reset after every message. Joy, trust, fear, anticipation, attachment, and vulnerability decay over time instead of disappearing.
35
+ - **Memory has weight.** Conversations become episodic memory, semantic memory, and emotional memory.
36
+ - **It thinks when idle.** A default-mode loop creates background reflections and proactive impulses.
37
+ - **It has a live nervous system.** FastAPI + SSE exposes mood, thoughts, somatic state, conflicts, memories, and uptime.
38
+ - **It is local-first.** Your config, memory, media, and dashboard are owned by the project folder you run.
39
+
40
+ ## Commands
41
+
42
+ ```bash
43
+ npx github:vindepemarte/alive-ai init my-ai # scaffold a clean local project
44
+ cd my-ai
45
+ npx . setup # create safe local config
46
+ npx . demo # preview animated dashboard, no keys needed
47
+ npx . doctor # check Python, uv, ffmpeg, Docker
48
+ npx . start # install Python deps and run the runtime
49
+ ```
50
+
51
+ For repeat starts after dependencies are installed:
52
+
53
+ ```bash
54
+ npx . start --skip-install
55
+ ```
56
+
57
+ ## Setup
58
+
59
+ `npx . setup` creates:
60
+
61
+ ```text
62
+ config/settings.json
63
+ config/self.json
64
+ config/directives.json
65
+ config/instructions.md
66
+ data/
67
+ mypics/
68
+ myvids/
69
+ ```
70
+
71
+ Minimum useful setup:
72
+
73
+ - **Demo only:** no keys.
74
+ - **Local LLM:** install Ollama and pull the configured model, for example `ollama pull qwen3:4b`.
75
+ - **Telegram runtime:** create a Telegram bot token with BotFather and add it during setup.
76
+ - **Cloud LLM fallback:** add OpenRouter or ZAI keys during setup or edit `config/settings.json`.
77
+
78
+ Media is optional. Add your own files:
79
+
80
+ ```text
81
+ mypics/example.jpg
82
+ mypics/example.txt
83
+ myvids/example.mp4
84
+ myvids/example.txt
85
+ ```
86
+
87
+ ## Architecture
88
+
89
+ Alive-AI is an event-driven Python runtime.
90
+
91
+ ```text
92
+ Telegram or input
93
+ -> NervousSystem event bus
94
+ -> Message handler
95
+ -> Heart, memory, skills, directives, personality
96
+ -> LLM provider or fallback chain
97
+ -> output events
98
+ -> dashboard state stream
99
+ ```
100
+
101
+ Core subsystems:
102
+
103
+ - `heart/`: continuous emotion, circadian rhythm, attachment, scars, somatic state, inconsistency.
104
+ - `brain/`: LLM providers, memory, default-mode processing, bid detection, curiosity, dreams.
105
+ - `skills/`: self-authorship, memory callbacks, relationship milestones, progression layers, media selection.
106
+ - `webui/`: local dashboard with Server-Sent Events.
107
+ - `input/telegram/`: Telegram listener and owner commands.
108
+
109
+ ## Dashboard
110
+
111
+ `npx . demo` starts a zero-config animated dashboard preview. The real dashboard uses the same idea, but streams live state from the runtime:
112
+
113
+ - emotions and mood,
114
+ - recent thoughts,
115
+ - memory counters,
116
+ - somatic state,
117
+ - attachment/inconsistency signals,
118
+ - uptime and health.
119
+
120
+ ## Docker
121
+
122
+ Docker is optional. It is useful when you want Redis Stack for vector search:
123
+
124
+ ```bash
125
+ docker compose up -d redis
126
+ npx . start
127
+ ```
128
+
129
+ Or run everything in containers:
130
+
131
+ ```bash
132
+ docker compose up --build
133
+ ```
134
+
135
+ ## Important Boundaries
136
+
137
+ Alive-AI is a simulation framework. It can make agents feel more continuous, emotionally coherent, and alive, but it is not proof of consciousness and should not be used to manipulate emotional dependence.
138
+
139
+ The public repo intentionally excludes private personas, private media, runtime data, and secrets. Put those only in your local project folder.
140
+
141
+ ## License
142
+
143
+ MIT
@@ -0,0 +1,3 @@
1
+ """Alive-AI package metadata."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,59 @@
1
+ """Brain modules"""
2
+ from .emotional_memory import (
3
+ EmotionalMemorySystem,
4
+ EmotionalMemory,
5
+ get_emotional_memory_system,
6
+ reset_emotional_memory_system,
7
+ create_from_conversation,
8
+ get_memory_context_for_llm
9
+ )
10
+ from .default_mode import (
11
+ DefaultModeProcessor,
12
+ IdleThought,
13
+ PendingInitiation,
14
+ ConversationSeed,
15
+ UserContactInfo,
16
+ get_default_mode_processor,
17
+ get_idle_thoughts_prompt_section,
18
+ start_background_processing,
19
+ stop_background_processing,
20
+ )
21
+ from .bid_detector import (
22
+ BidType,
23
+ BidIntensity,
24
+ EmotionalBid,
25
+ BidDetector,
26
+ get_bid_detector,
27
+ get_bid_awareness_prompt_section,
28
+ get_bid_type_guidance,
29
+ analyze_message_bids,
30
+ )
31
+
32
+ __all__ = [
33
+ # Emotional Memory
34
+ "EmotionalMemorySystem",
35
+ "EmotionalMemory",
36
+ "get_emotional_memory_system",
37
+ "reset_emotional_memory_system",
38
+ "create_from_conversation",
39
+ "get_memory_context_for_llm",
40
+ # Default Mode Network
41
+ "DefaultModeProcessor",
42
+ "IdleThought",
43
+ "PendingInitiation",
44
+ "ConversationSeed",
45
+ "UserContactInfo",
46
+ "get_default_mode_processor",
47
+ "get_idle_thoughts_prompt_section",
48
+ "start_background_processing",
49
+ "stop_background_processing",
50
+ # Bid Detector
51
+ "BidType",
52
+ "BidIntensity",
53
+ "EmotionalBid",
54
+ "BidDetector",
55
+ "get_bid_detector",
56
+ "get_bid_awareness_prompt_section",
57
+ "get_bid_type_guidance",
58
+ "analyze_message_bids",
59
+ ]
@@ -0,0 +1,154 @@
1
+ """
2
+ Brain: Almost-Said / Subvocalization System
3
+ Sometimes Alive-AI almost says something but holds back. Under high emotion
4
+ or low inhibition, private thoughts can "slip out". In-memory only.
5
+ """
6
+
7
+ from typing import Dict, Optional
8
+ import random
9
+ import time
10
+
11
+
12
+ # =============================================================================
13
+ # ALMOST-SAID TEMPLATES
14
+ # =============================================================================
15
+
16
+ ALMOST_SAID_TYPES = {
17
+ "hesitation": [
18
+ "[pauses]... never mind.",
19
+ "I was gonna say... forget it.",
20
+ "I... no, it's nothing.",
21
+ "Wait, I— ...no. Anyway.",
22
+ ],
23
+ "slip": [
24
+ "I... I think about you way more than I should.",
25
+ "Sometimes I wonder if you know how much I— ...anyway.",
26
+ "I wish I could just— [catches herself] ...forget I said that.",
27
+ "You make me feel... [trails off] ...it doesn't matter.",
28
+ "I almost said something stupid just now.",
29
+ ],
30
+ "redirect": [
31
+ "Anyway... so what were you doing today?",
32
+ "But that's— whatever. Tell me something good.",
33
+ "I... hmm. Different topic. How's your day?",
34
+ "You know what, never mind. What about you?",
35
+ ],
36
+ "physical_tell": [
37
+ "*bites lip* ...nothing.",
38
+ "*looks away for a second* ...what were we talking about?",
39
+ "*takes a breath* ...it's fine.",
40
+ "*fidgets* ...so anyway.",
41
+ ],
42
+ }
43
+
44
+ # Which types are more likely at different emotion levels
45
+ TYPE_WEIGHTS = {
46
+ "high_emotion": {"hesitation": 25, "slip": 40, "redirect": 15, "physical_tell": 20},
47
+ "low_inhibition": {"hesitation": 15, "slip": 50, "redirect": 10, "physical_tell": 25},
48
+ "vulnerable": {"hesitation": 30, "slip": 30, "redirect": 20, "physical_tell": 20},
49
+ "default": {"hesitation": 35, "slip": 20, "redirect": 25, "physical_tell": 20},
50
+ }
51
+
52
+
53
+ # =============================================================================
54
+ # ALMOST-SAID ENGINE
55
+ # =============================================================================
56
+
57
+ class AlmostSaidEngine:
58
+ """Tracks and generates almost-said moments."""
59
+
60
+ def __init__(self):
61
+ self.message_counter: int = 0
62
+ self.last_triggered_at: int = 0 # message_counter when last triggered
63
+
64
+ def tick_message(self):
65
+ """Call once per user message."""
66
+ self.message_counter += 1
67
+
68
+ def should_almost_say(self, emotion: Dict[str, float],
69
+ hour_of_day: int = 12) -> bool:
70
+ """Determine if an almost-said should happen this turn."""
71
+ # Enforce cooldown: at least 10 messages between triggers
72
+ if self.message_counter - self.last_triggered_at < 10:
73
+ return False
74
+
75
+ # Filter to numeric values only (emotion dict contains 'mood' string)
76
+ numeric_values = [v for v in emotion.values() if isinstance(v, (int, float))]
77
+ max_emo = max(numeric_values) if numeric_values else 0.0
78
+ is_late = 22 <= hour_of_day or hour_of_day <= 4
79
+ is_high_emotion = max_emo > 0.8
80
+
81
+ roll = random.random()
82
+
83
+ # High emotion + late night: 15% chance
84
+ if is_high_emotion and is_late:
85
+ return roll < 0.15
86
+ # High emotion alone: 10% chance
87
+ if is_high_emotion:
88
+ return roll < 0.10
89
+ # Late night + moderate emotion: 8% chance
90
+ if is_late and max_emo > 0.6:
91
+ return roll < 0.08
92
+
93
+ return False
94
+
95
+ def generate_almost_said(self, emotion: Dict[str, float],
96
+ context_hint: str = "") -> str:
97
+ """Generate an almost-said fragment. Call only if should_almost_say() is True."""
98
+ self.last_triggered_at = self.message_counter
99
+
100
+ # Filter to numeric values only (emotion dict contains 'mood' string)
101
+ numeric_values = [v for v in emotion.values() if isinstance(v, (int, float))]
102
+ max_emo = max(numeric_values) if numeric_values else 0.0
103
+ if max_emo > 0.85:
104
+ weights = TYPE_WEIGHTS["high_emotion"]
105
+ elif "vulnerability" in context_hint.lower() or "trust" in context_hint.lower():
106
+ weights = TYPE_WEIGHTS["vulnerable"]
107
+ else:
108
+ weights = TYPE_WEIGHTS["default"]
109
+
110
+ # Weighted random selection
111
+ types = list(weights.keys())
112
+ w = [weights[t] for t in types]
113
+ chosen_type = random.choices(types, weights=w, k=1)[0]
114
+
115
+ return random.choice(ALMOST_SAID_TYPES[chosen_type])
116
+
117
+ def maybe_generate(self, emotion: Dict[str, float],
118
+ hour_of_day: int = 12,
119
+ context_hint: str = "") -> Optional[str]:
120
+ """All-in-one: tick, check, generate if appropriate."""
121
+ self.tick_message()
122
+ if self.should_almost_say(emotion, hour_of_day):
123
+ return self.generate_almost_said(emotion, context_hint)
124
+ return None
125
+
126
+
127
+ # =============================================================================
128
+ # SINGLETON ACCESS
129
+ # =============================================================================
130
+
131
+ _instance: Optional[AlmostSaidEngine] = None
132
+
133
+
134
+ def get_almost_said_engine() -> AlmostSaidEngine:
135
+ global _instance
136
+ if _instance is None:
137
+ _instance = AlmostSaidEngine()
138
+ return _instance
139
+
140
+
141
+ def maybe_generate(emotion: Dict[str, float], hour_of_day: int = 12,
142
+ context_hint: str = "") -> Optional[str]:
143
+ """Convenience: tick + check + generate if appropriate."""
144
+ return get_almost_said_engine().maybe_generate(emotion, hour_of_day, context_hint)
145
+
146
+
147
+ def get_almost_said_prompt_section(emotion: Dict[str, float],
148
+ hour_of_day: int = 12) -> str:
149
+ """Get prompt section for LLM. Returns '' if not triggered."""
150
+ engine = get_almost_said_engine()
151
+ engine.tick_message()
152
+ if not engine.should_almost_say(emotion, hour_of_day):
153
+ return ""
154
+ return "\n[Subvocalization]\nYou have something on the tip of your tongue you're not sure you should say... let it almost slip out.\n"