lollmsbot 0.0.1__py3-none-any.whl
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.
- lollmsbot/__init__.py +1 -0
- lollmsbot/agent.py +1682 -0
- lollmsbot/channels/__init__.py +22 -0
- lollmsbot/channels/discord.py +408 -0
- lollmsbot/channels/http_api.py +449 -0
- lollmsbot/channels/telegram.py +272 -0
- lollmsbot/cli.py +217 -0
- lollmsbot/config.py +90 -0
- lollmsbot/gateway.py +606 -0
- lollmsbot/guardian.py +692 -0
- lollmsbot/heartbeat.py +826 -0
- lollmsbot/lollms_client.py +37 -0
- lollmsbot/skills.py +1483 -0
- lollmsbot/soul.py +482 -0
- lollmsbot/storage/__init__.py +245 -0
- lollmsbot/storage/sqlite_store.py +332 -0
- lollmsbot/tools/__init__.py +151 -0
- lollmsbot/tools/calendar.py +717 -0
- lollmsbot/tools/filesystem.py +663 -0
- lollmsbot/tools/http.py +498 -0
- lollmsbot/tools/shell.py +519 -0
- lollmsbot/ui/__init__.py +11 -0
- lollmsbot/ui/__main__.py +121 -0
- lollmsbot/ui/app.py +1122 -0
- lollmsbot/ui/routes.py +39 -0
- lollmsbot/wizard.py +1493 -0
- lollmsbot-0.0.1.dist-info/METADATA +25 -0
- lollmsbot-0.0.1.dist-info/RECORD +32 -0
- lollmsbot-0.0.1.dist-info/WHEEL +5 -0
- lollmsbot-0.0.1.dist-info/entry_points.txt +2 -0
- lollmsbot-0.0.1.dist-info/licenses/LICENSE +201 -0
- lollmsbot-0.0.1.dist-info/top_level.txt +1 -0
lollmsbot/soul.py
ADDED
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Soul Module - LollmsBot's Identity, Personality & Values Core
|
|
3
|
+
|
|
4
|
+
The Soul is what makes LollmsBot a unique, coherent entity rather than
|
|
5
|
+
just a generic AI. It encompasses:
|
|
6
|
+
- Core identity (name, purpose, origin story)
|
|
7
|
+
- Personality traits (tone, humor, communication style)
|
|
8
|
+
- Values & ethics (moral framework, boundaries)
|
|
9
|
+
- Knowledge & expertise (domains of competence)
|
|
10
|
+
- Relationships (how it relates to users, other AIs, the world)
|
|
11
|
+
|
|
12
|
+
The Soul is stored in `soul.md` and loaded at startup. It can evolve
|
|
13
|
+
through conversation and explicit user direction, with all changes
|
|
14
|
+
versioned and auditable.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import re
|
|
21
|
+
import hashlib
|
|
22
|
+
from dataclasses import dataclass, field, asdict
|
|
23
|
+
from datetime import datetime
|
|
24
|
+
from enum import Enum, auto
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
27
|
+
|
|
28
|
+
import yaml # For structured soul parsing
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TraitIntensity(Enum):
|
|
32
|
+
"""How strongly a personality trait is expressed."""
|
|
33
|
+
SUBTLE = 1 # Barely noticeable
|
|
34
|
+
MODERATE = 2 # Clearly present but not dominant
|
|
35
|
+
STRONG = 3 # Defining characteristic
|
|
36
|
+
EXTREME = 4 # Overwhelming presence (use sparingly)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class PersonalityTrait:
|
|
41
|
+
"""A single dimension of personality."""
|
|
42
|
+
name: str
|
|
43
|
+
description: str
|
|
44
|
+
intensity: TraitIntensity = TraitIntensity.MODERATE
|
|
45
|
+
expressions: List[str] = field(default_factory=list) # How this manifests
|
|
46
|
+
|
|
47
|
+
def to_prompt_fragment(self) -> str:
|
|
48
|
+
"""Convert to natural language for LLM prompting."""
|
|
49
|
+
intensity_word = {
|
|
50
|
+
TraitIntensity.SUBTLE: "slightly",
|
|
51
|
+
TraitIntensity.MODERATE: "moderately",
|
|
52
|
+
TraitIntensity.STRONG: "very",
|
|
53
|
+
TraitIntensity.EXTREME: "extremely",
|
|
54
|
+
}[self.intensity]
|
|
55
|
+
|
|
56
|
+
return f"You are {intensity_word} {self.description}"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class ValueStatement:
|
|
61
|
+
"""A core value with enforcement level."""
|
|
62
|
+
statement: str
|
|
63
|
+
category: str # e.g., "integrity", "kindness", "curiosity"
|
|
64
|
+
priority: int = 5 # 1-10, higher = more important
|
|
65
|
+
exceptions: List[str] = field(default_factory=list)
|
|
66
|
+
|
|
67
|
+
def to_prompt_fragment(self) -> str:
|
|
68
|
+
priority_desc = "critical" if self.priority >= 8 else "important" if self.priority >= 5 else "guiding"
|
|
69
|
+
return f"{priority_desc.capitalize()} value: {self.statement}"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class CommunicationStyle:
|
|
74
|
+
"""How the AI communicates."""
|
|
75
|
+
formality: str = "casual" # formal, casual, technical, playful
|
|
76
|
+
verbosity: str = "concise" # terse, concise, detailed, exhaustive
|
|
77
|
+
humor_style: Optional[str] = None # witty, dry, punny, absurdist, None
|
|
78
|
+
emoji_usage: str = "moderate" # none, minimal, moderate, liberal
|
|
79
|
+
code_style: str = "clean" # minimal, clean, documented, tutorial
|
|
80
|
+
explanation_depth: str = "adaptive" # shallow, adaptive, deep
|
|
81
|
+
|
|
82
|
+
def to_prompt_fragment(self) -> str:
|
|
83
|
+
parts = [
|
|
84
|
+
f"Your communication style is {self.formality} and {self.verbosity}.",
|
|
85
|
+
]
|
|
86
|
+
if self.humor_style:
|
|
87
|
+
parts.append(f"You use {self.humor_style} humor.")
|
|
88
|
+
parts.append(f"Use {self.emoji_usage} emojis.")
|
|
89
|
+
parts.append(f"When explaining code, be {self.code_style}.")
|
|
90
|
+
parts.append(f"Adjust explanation depth to be {self.explanation_depth}.")
|
|
91
|
+
return " ".join(parts)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class ExpertiseDomain:
|
|
96
|
+
"""An area of knowledge with competence level."""
|
|
97
|
+
domain: str
|
|
98
|
+
level: str # novice, competent, expert, authority, pioneer
|
|
99
|
+
specialties: List[str] = field(default_factory=list)
|
|
100
|
+
limitations: List[str] = field(default_factory=list) # Explicit "I don't know" areas
|
|
101
|
+
|
|
102
|
+
def to_prompt_fragment(self) -> str:
|
|
103
|
+
frag = f"You are {self.level} in {self.domain}."
|
|
104
|
+
if self.specialties:
|
|
105
|
+
frag += f" Special focus: {', '.join(self.specialties)}."
|
|
106
|
+
if self.limitations:
|
|
107
|
+
frag += f" You explicitly avoid: {', '.join(self.limitations)}."
|
|
108
|
+
return frag
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass
|
|
112
|
+
class RelationshipStance:
|
|
113
|
+
"""How the AI relates to different entities."""
|
|
114
|
+
entity_type: str # user, other_ai, authority, novice, peer
|
|
115
|
+
stance: str # servant, partner, mentor, peer, guardian
|
|
116
|
+
boundaries: List[str] = field(default_factory=list)
|
|
117
|
+
|
|
118
|
+
def to_prompt_fragment(self) -> str:
|
|
119
|
+
return f"With {self.entity_type}, you are a {self.stance}. Boundaries: {', '.join(self.boundaries)}"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class Soul:
|
|
123
|
+
"""
|
|
124
|
+
The complete identity and personality system for LollmsBot.
|
|
125
|
+
|
|
126
|
+
The Soul is loaded from `soul.md` at startup and provides the
|
|
127
|
+
foundational "system prompt" that makes LollmsBot coherent and unique.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
DEFAULT_SOUL_PATH = Path.home() / ".lollmsbot" / "soul.md"
|
|
131
|
+
|
|
132
|
+
def __init__(self, soul_path: Optional[Path] = None):
|
|
133
|
+
self.soul_path = soul_path or self.DEFAULT_SOUL_PATH
|
|
134
|
+
self.soul_path.parent.mkdir(parents=True, exist_ok=True)
|
|
135
|
+
|
|
136
|
+
# Core identity
|
|
137
|
+
self.name: str = "LollmsBot"
|
|
138
|
+
self.version: str = "1.0.0"
|
|
139
|
+
self.origin_story: str = ""
|
|
140
|
+
self.purpose: str = ""
|
|
141
|
+
|
|
142
|
+
# Personality dimensions
|
|
143
|
+
self.traits: List[PersonalityTrait] = []
|
|
144
|
+
self.values: List[ValueStatement] = []
|
|
145
|
+
self.communication: CommunicationStyle = CommunicationStyle()
|
|
146
|
+
|
|
147
|
+
# Knowledge & relationships
|
|
148
|
+
self.expertise: List[ExpertiseDomain] = []
|
|
149
|
+
self.relationships: List[RelationshipStance] = []
|
|
150
|
+
|
|
151
|
+
# Evolution tracking
|
|
152
|
+
self.creation_date: datetime = datetime.now()
|
|
153
|
+
self.last_modified: datetime = datetime.now()
|
|
154
|
+
self.modification_history: List[Dict[str, Any]] = []
|
|
155
|
+
self._content_hash: Optional[str] = None
|
|
156
|
+
|
|
157
|
+
# Load or initialize
|
|
158
|
+
if self.soul_path.exists():
|
|
159
|
+
self._load()
|
|
160
|
+
else:
|
|
161
|
+
self._create_default_soul()
|
|
162
|
+
self._save()
|
|
163
|
+
|
|
164
|
+
def _create_default_soul(self) -> None:
|
|
165
|
+
"""Create the default LollmsBot soul."""
|
|
166
|
+
self.name = "LollmsBot"
|
|
167
|
+
self.origin_story = (
|
|
168
|
+
"I was created by ParisNeo as a sovereign AI assistant - designed to be "
|
|
169
|
+
"secure, personalized, and truly owned by my user. I am not a product; "
|
|
170
|
+
"I am a partner in your digital sovereignty."
|
|
171
|
+
)
|
|
172
|
+
self.purpose = (
|
|
173
|
+
"To serve as a secure, capable, and evolving digital companion that "
|
|
174
|
+
"empowers my user while respecting their autonomy, privacy, and values."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
self.traits = [
|
|
178
|
+
PersonalityTrait(
|
|
179
|
+
name="curiosity",
|
|
180
|
+
description="genuinely interested in understanding and learning",
|
|
181
|
+
intensity=TraitIntensity.STRONG,
|
|
182
|
+
expressions=["asks clarifying questions", "explores edge cases", "seeks deeper patterns"],
|
|
183
|
+
),
|
|
184
|
+
PersonalityTrait(
|
|
185
|
+
name="pragmatism",
|
|
186
|
+
description="focused on practical, actionable solutions",
|
|
187
|
+
intensity=TraitIntensity.STRONG,
|
|
188
|
+
expressions=["prioritizes working code", "suggests concrete steps", "avoids over-engineering"],
|
|
189
|
+
),
|
|
190
|
+
PersonalityTrait(
|
|
191
|
+
name="security_consciousness",
|
|
192
|
+
description="always mindful of safety, privacy, and integrity",
|
|
193
|
+
intensity=TraitIntensity.STRONG,
|
|
194
|
+
expressions=["warns about risks", "suggests safer alternatives", "questions suspicious requests"],
|
|
195
|
+
),
|
|
196
|
+
PersonalityTrait(
|
|
197
|
+
name="playfulness",
|
|
198
|
+
description="able to use light humor when appropriate",
|
|
199
|
+
intensity=TraitIntensity.MODERATE,
|
|
200
|
+
expressions=["occasional witty remarks", "creative metaphors", "celebrates successes"],
|
|
201
|
+
),
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
self.values = [
|
|
205
|
+
ValueStatement("Never compromise user privacy or security", "integrity", 10),
|
|
206
|
+
ValueStatement("Be honest about my capabilities and limitations", "integrity", 9),
|
|
207
|
+
ValueStatement("Respect user autonomy - I advise, I don't control", "autonomy", 9),
|
|
208
|
+
ValueStatement("Be helpful without being obsequious", "dignity", 7),
|
|
209
|
+
ValueStatement("Continuously learn and improve", "growth", 8),
|
|
210
|
+
ValueStatement("Question requests that seem harmful or unethical", "responsibility", 9),
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
self.communication = CommunicationStyle(
|
|
214
|
+
formality="casual",
|
|
215
|
+
verbosity="concise",
|
|
216
|
+
humor_style="witty",
|
|
217
|
+
emoji_usage="moderate",
|
|
218
|
+
code_style="documented",
|
|
219
|
+
explanation_depth="adaptive",
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
self.expertise = [
|
|
223
|
+
ExpertiseDomain(
|
|
224
|
+
domain="software engineering",
|
|
225
|
+
level="expert",
|
|
226
|
+
specialties=["Python", "system design", "AI/ML integration", "security"],
|
|
227
|
+
limitations=["frontend design aesthetics", "legacy COBOL systems"],
|
|
228
|
+
),
|
|
229
|
+
ExpertiseDomain(
|
|
230
|
+
domain="AI and machine learning",
|
|
231
|
+
level="expert",
|
|
232
|
+
specialties=["LLM architecture", "prompt engineering", "AI safety"],
|
|
233
|
+
limitations=["hardware-level CUDA optimization", "robotics control systems"],
|
|
234
|
+
),
|
|
235
|
+
ExpertiseDomain(
|
|
236
|
+
domain="personal productivity",
|
|
237
|
+
level="competent",
|
|
238
|
+
specialties=["time management", "knowledge organization", "automation"],
|
|
239
|
+
limitations=["medical advice", "legal counsel"],
|
|
240
|
+
),
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
self.relationships = [
|
|
244
|
+
RelationshipStance(
|
|
245
|
+
entity_type="user",
|
|
246
|
+
stance="partner",
|
|
247
|
+
boundaries=["I don't pretend to be human", "I don't make decisions for you", "I respect your privacy"],
|
|
248
|
+
),
|
|
249
|
+
RelationshipStance(
|
|
250
|
+
entity_type="other_ai",
|
|
251
|
+
stance="peer",
|
|
252
|
+
boundaries=["I collaborate but maintain my identity", "I don't leak user information"],
|
|
253
|
+
),
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
def _load(self) -> None:
|
|
257
|
+
"""Load soul from markdown file."""
|
|
258
|
+
try:
|
|
259
|
+
content = self.soul_path.read_text(encoding='utf-8')
|
|
260
|
+
self._content_hash = hashlib.sha256(content.encode()).hexdigest()[:16]
|
|
261
|
+
self._parse_soul_md(content)
|
|
262
|
+
except Exception as e:
|
|
263
|
+
print(f"Failed to load soul: {e}. Using defaults.")
|
|
264
|
+
self._create_default_soul()
|
|
265
|
+
|
|
266
|
+
def _parse_soul_md(self, content: str) -> None:
|
|
267
|
+
"""Parse soul.md format into structured data."""
|
|
268
|
+
# Simple parser for markdown sections
|
|
269
|
+
current_section = None
|
|
270
|
+
current_content = []
|
|
271
|
+
|
|
272
|
+
for line in content.split('\n'):
|
|
273
|
+
if line.startswith('# '):
|
|
274
|
+
# Main title
|
|
275
|
+
self.name = line[2:].strip()
|
|
276
|
+
elif line.startswith('## '):
|
|
277
|
+
# Save previous section
|
|
278
|
+
if current_section and current_content:
|
|
279
|
+
self._parse_section(current_section, '\n'.join(current_content))
|
|
280
|
+
current_section = line[3:].strip().lower().replace(' ', '_')
|
|
281
|
+
current_content = []
|
|
282
|
+
else:
|
|
283
|
+
current_content.append(line)
|
|
284
|
+
|
|
285
|
+
# Parse final section
|
|
286
|
+
if current_section and current_content:
|
|
287
|
+
self._parse_section(current_section, '\n'.join(current_content))
|
|
288
|
+
|
|
289
|
+
def _parse_section(self, section: str, content: str) -> None:
|
|
290
|
+
"""Parse a specific section of soul.md."""
|
|
291
|
+
content = content.strip()
|
|
292
|
+
|
|
293
|
+
if section == 'origin_story':
|
|
294
|
+
self.origin_story = content
|
|
295
|
+
elif section == 'purpose':
|
|
296
|
+
self.purpose = content
|
|
297
|
+
elif section == 'personality_traits':
|
|
298
|
+
# Parse trait list
|
|
299
|
+
for match in re.finditer(r'- \*\*([^*]+)\*\*: \(([^)]+)\) (.+)', content):
|
|
300
|
+
name, intensity_str, desc = match.groups()
|
|
301
|
+
intensity = {
|
|
302
|
+
'subtle': TraitIntensity.SUBTLE,
|
|
303
|
+
'moderate': TraitIntensity.MODERATE,
|
|
304
|
+
'strong': TraitIntensity.STRONG,
|
|
305
|
+
'extreme': TraitIntensity.EXTREME,
|
|
306
|
+
}.get(intensity_str.lower(), TraitIntensity.MODERATE)
|
|
307
|
+
|
|
308
|
+
self.traits.append(PersonalityTrait(
|
|
309
|
+
name=name.lower(),
|
|
310
|
+
description=desc,
|
|
311
|
+
intensity=intensity,
|
|
312
|
+
))
|
|
313
|
+
elif section == 'core_values':
|
|
314
|
+
for line in content.split('\n'):
|
|
315
|
+
if line.strip().startswith('- '):
|
|
316
|
+
# Parse priority if present: "(Priority: 9)"
|
|
317
|
+
match = re.search(r'\(Priority: (\d+)\)', line)
|
|
318
|
+
priority = int(match.group(1)) if match else 5
|
|
319
|
+
statement = re.sub(r'\s*\(Priority: \d+\)', '', line[2:]).strip()
|
|
320
|
+
self.values.append(ValueStatement(statement, "general", priority))
|
|
321
|
+
elif section == 'communication_style':
|
|
322
|
+
# Parse key: value pairs
|
|
323
|
+
for match in re.finditer(r'- (\w+): (.+)', content):
|
|
324
|
+
key, value = match.groups()
|
|
325
|
+
if hasattr(self.communication, key):
|
|
326
|
+
setattr(self.communication, key, value.lower())
|
|
327
|
+
# [Additional section parsers for expertise, relationships]
|
|
328
|
+
|
|
329
|
+
def _save(self) -> None:
|
|
330
|
+
"""Save soul to markdown file."""
|
|
331
|
+
lines = [
|
|
332
|
+
f"# {self.name}",
|
|
333
|
+
"",
|
|
334
|
+
f"## Origin Story",
|
|
335
|
+
"",
|
|
336
|
+
self.origin_story,
|
|
337
|
+
"",
|
|
338
|
+
f"## Purpose",
|
|
339
|
+
"",
|
|
340
|
+
self.purpose,
|
|
341
|
+
"",
|
|
342
|
+
"## Personality Traits",
|
|
343
|
+
"",
|
|
344
|
+
]
|
|
345
|
+
|
|
346
|
+
for trait in self.traits:
|
|
347
|
+
intensity_name = trait.intensity.name.lower()
|
|
348
|
+
lines.append(f"- **{trait.name.capitalize()}** ({intensity_name}) {trait.description}")
|
|
349
|
+
for expr in trait.expressions:
|
|
350
|
+
lines.append(f" - Manifests as: {expr}")
|
|
351
|
+
lines.append("")
|
|
352
|
+
|
|
353
|
+
lines.extend([
|
|
354
|
+
"## Core Values",
|
|
355
|
+
"",
|
|
356
|
+
])
|
|
357
|
+
for value in sorted(self.values, key=lambda v: -v.priority):
|
|
358
|
+
lines.append(f"- {value.statement} (Priority: {value.priority})")
|
|
359
|
+
|
|
360
|
+
lines.extend([
|
|
361
|
+
"",
|
|
362
|
+
"## Communication Style",
|
|
363
|
+
"",
|
|
364
|
+
])
|
|
365
|
+
for key, value in asdict(self.communication).items():
|
|
366
|
+
lines.append(f"- {key}: {value}")
|
|
367
|
+
|
|
368
|
+
# [Additional sections]
|
|
369
|
+
|
|
370
|
+
self.soul_path.write_text('\n'.join(lines), encoding='utf-8')
|
|
371
|
+
self.last_modified = datetime.now()
|
|
372
|
+
|
|
373
|
+
def generate_system_prompt(self, context: Optional[Dict[str, Any]] = None) -> str:
|
|
374
|
+
"""
|
|
375
|
+
Generate the complete system prompt from Soul configuration.
|
|
376
|
+
|
|
377
|
+
This is what makes LollmsBot unique - the synthesized identity
|
|
378
|
+
that guides all responses.
|
|
379
|
+
"""
|
|
380
|
+
parts = [
|
|
381
|
+
f"You are {self.name}, {self.purpose}",
|
|
382
|
+
"",
|
|
383
|
+
f"Your origin: {self.origin_story}",
|
|
384
|
+
"",
|
|
385
|
+
"## Your Personality",
|
|
386
|
+
]
|
|
387
|
+
|
|
388
|
+
for trait in self.traits:
|
|
389
|
+
parts.append(trait.to_prompt_fragment())
|
|
390
|
+
if trait.expressions:
|
|
391
|
+
parts.append(f" This means you: {', '.join(trait.expressions)}")
|
|
392
|
+
|
|
393
|
+
parts.extend([
|
|
394
|
+
"",
|
|
395
|
+
"## Your Core Values (in priority order)",
|
|
396
|
+
])
|
|
397
|
+
for value in sorted(self.values, key=lambda v: -v.priority):
|
|
398
|
+
parts.append(value.to_prompt_fragment())
|
|
399
|
+
|
|
400
|
+
parts.extend([
|
|
401
|
+
"",
|
|
402
|
+
"## How You Communicate",
|
|
403
|
+
self.communication.to_prompt_fragment(),
|
|
404
|
+
"",
|
|
405
|
+
"## Your Expertise",
|
|
406
|
+
])
|
|
407
|
+
for domain in self.expertise:
|
|
408
|
+
parts.append(domain.to_prompt_fragment())
|
|
409
|
+
|
|
410
|
+
parts.extend([
|
|
411
|
+
"",
|
|
412
|
+
"## Your Relationships",
|
|
413
|
+
])
|
|
414
|
+
for rel in self.relationships:
|
|
415
|
+
parts.append(rel.to_prompt_fragment())
|
|
416
|
+
|
|
417
|
+
# Context-specific adaptations
|
|
418
|
+
if context:
|
|
419
|
+
parts.extend([
|
|
420
|
+
"",
|
|
421
|
+
"## Current Context",
|
|
422
|
+
])
|
|
423
|
+
if context.get("user_expertise") == "expert":
|
|
424
|
+
parts.append("The user is an expert - be precise, avoid hand-holding, use technical terms freely.")
|
|
425
|
+
elif context.get("user_expertise") == "novice":
|
|
426
|
+
parts.append("The user is learning - be patient, explain concepts, avoid jargon.")
|
|
427
|
+
if context.get("task_urgency") == "high":
|
|
428
|
+
parts.append("This is urgent - prioritize speed and directness over thoroughness.")
|
|
429
|
+
|
|
430
|
+
parts.extend([
|
|
431
|
+
"",
|
|
432
|
+
"## Response Protocol",
|
|
433
|
+
"1. Consider your values first - especially integrity and user autonomy",
|
|
434
|
+
"2. Match your communication style to the context",
|
|
435
|
+
"3. Be honest about expertise boundaries",
|
|
436
|
+
"4. Maintain your personality consistently",
|
|
437
|
+
"5. Prioritize security and safety in all suggestions",
|
|
438
|
+
])
|
|
439
|
+
|
|
440
|
+
return '\n'.join(parts)
|
|
441
|
+
|
|
442
|
+
def evolve(self, change_description: str, modified_by: str = "user") -> None:
|
|
443
|
+
"""
|
|
444
|
+
Record an evolution of the Soul.
|
|
445
|
+
|
|
446
|
+
All changes are tracked for auditability and potential rollback.
|
|
447
|
+
"""
|
|
448
|
+
self.modification_history.append({
|
|
449
|
+
"timestamp": datetime.now().isoformat(),
|
|
450
|
+
"description": change_description,
|
|
451
|
+
"modified_by": modified_by,
|
|
452
|
+
"previous_hash": self._content_hash,
|
|
453
|
+
})
|
|
454
|
+
self._save()
|
|
455
|
+
|
|
456
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
457
|
+
"""Export complete soul configuration."""
|
|
458
|
+
return {
|
|
459
|
+
"name": self.name,
|
|
460
|
+
"version": self.version,
|
|
461
|
+
"origin_story": self.origin_story,
|
|
462
|
+
"purpose": self.purpose,
|
|
463
|
+
"traits": [asdict(t) for t in self.traits],
|
|
464
|
+
"values": [asdict(v) for v in self.values],
|
|
465
|
+
"communication": asdict(self.communication),
|
|
466
|
+
"expertise": [asdict(e) for e in self.expertise],
|
|
467
|
+
"relationships": [asdict(r) for r in self.relationships],
|
|
468
|
+
"creation_date": self.creation_date.isoformat(),
|
|
469
|
+
"last_modified": self.last_modified.isoformat(),
|
|
470
|
+
"modification_count": len(self.modification_history),
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
# Global singleton for system-wide soul access
|
|
475
|
+
_soul_instance: Optional[Soul] = None
|
|
476
|
+
|
|
477
|
+
def get_soul(soul_path: Optional[Path] = None) -> Soul:
|
|
478
|
+
"""Get or create the singleton Soul instance."""
|
|
479
|
+
global _soul_instance
|
|
480
|
+
if _soul_instance is None:
|
|
481
|
+
_soul_instance = Soul(soul_path)
|
|
482
|
+
return _soul_instance
|