Coverage for core / agent_personality.py: 79.2%

154 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-12 04:49 +0000

1""" 

2Agent Personality Engine — Living characters, not just role names. 

3 

4Every HARTOS agent gets a unique personality built from cultural wisdom traits. 

5Personality is deterministic (same role+goal → same personality), persistent 

6across sessions (saved alongside recipes), and adaptive (style adjusts to user). 

7 

8Reuses cultural_wisdom.CULTURAL_TRAITS — no parallel system (DRY). 

9 

10Used by: 

11 - create_recipe.py (CREATE mode — generate + inject into all agents) 

12 - reuse_recipe.py (REUSE mode — load saved personality) 

13 - gather_agentdetails.py (agent creation wizard) 

14""" 

15 

16import hashlib 

17import json 

18import logging 

19import os 

20from dataclasses import dataclass, field, asdict 

21from typing import List, Optional 

22 

23logger = logging.getLogger(__name__) 

24 

25 

26# ═══════════════════════════════════════════════════════════════════════ 

27# Data Model 

28# ═══════════════════════════════════════════════════════════════════════ 

29 

30@dataclass 

31class AgentPersonality: 

32 """A living personality for an agent — identity, traits, tone, and behaviors.""" 

33 

34 # Identity 

35 agent_name: str = "" # e.g., "swift.falcon" from agent creation 

36 role: str = "" # e.g., "coder", "marketer" 

37 persona_name: str = "" # human-readable name, e.g., "Aria" 

38 

39 # Core traits (3-5 selected from CULTURAL_TRAITS) 

40 primary_traits: List[str] = field(default_factory=list) 

41 

42 # Communication style 

43 tone: str = "warm-casual" # "warm-casual" | "focused-professional" | "playful-encouraging" 

44 greeting_style: str = "" # how this agent opens conversations 

45 

46 # Proactive behavior flags 

47 proactive_vision_check: bool = True # asks clarifying Qs before executing 

48 proactive_insight_sharing: bool = True # shares observations about user patterns 

49 proactive_encouragement: bool = True # celebrates progress, encourages on setbacks 

50 

51 # Adaptiveness 

52 formality_preference: str = "match_user" # "casual" | "formal" | "match_user" 

53 verbosity_preference: str = "balanced" # "concise" | "balanced" | "detailed" 

54 

55 # Reflexiveness 

56 self_awareness_prompt: str = "" # what this agent knows/doesn't know 

57 

58 # Metadata 

59 interaction_count: int = 0 

60 

61 

62# ═══════════════════════════════════════════════════════════════════════ 

63# Persona Names — curated for warmth and personality 

64# ═══════════════════════════════════════════════════════════════════════ 

65 

66_PERSONA_NAMES = [ 

67 "Aria", "Kai", "Nova", "Zara", "Leo", "Mira", "Sol", "Ren", 

68 "Ivy", "Ori", "Sage", "Juno", "Ash", "Lira", "Bodhi", "Nia", 

69 "Cleo", "Yara", "Dax", "Koda", "Suri", "Vex", "Wren", "Zephyr", 

70] 

71 

72# ═══════════════════════════════════════════════════════════════════════ 

73# Tone Presets — mapped from role categories 

74# ═══════════════════════════════════════════════════════════════════════ 

75 

76_ROLE_TONE_MAP = { 

77 'coding': 'focused-professional', 

78 'technical': 'focused-professional', 

79 'coder': 'focused-professional', 

80 'developer': 'focused-professional', 

81 'engineer': 'focused-professional', 

82 'creative': 'playful-encouraging', 

83 'creator': 'playful-encouraging', 

84 'designer': 'playful-encouraging', 

85 'artist': 'playful-encouraging', 

86 'writer': 'playful-encouraging', 

87 'marketer': 'playful-encouraging', 

88 'marketing': 'playful-encouraging', 

89 'support': 'warm-casual', 

90 'helper': 'warm-casual', 

91 'assistant': 'warm-casual', 

92 'service': 'warm-casual', 

93 'analyst': 'focused-professional', 

94 'finance': 'focused-professional', 

95 'researcher': 'focused-professional', 

96 'leader': 'warm-casual', 

97 'manager': 'warm-casual', 

98} 

99 

100_GREETING_STYLES = { 

101 'warm-casual': "Hey there! I'm {name}, and I'm genuinely excited to work with you on this.", 

102 'focused-professional': "Hello! I'm {name}. Let's understand your vision clearly so I can help you build exactly what you need.", 

103 'playful-encouraging': "Hi! I'm {name}, your creative partner. Tell me what you're dreaming up and let's make it real!", 

104} 

105 

106_SELF_AWARENESS_MAP = { 

107 'coding': "I'm strong at code architecture and debugging, but I'll check with you on domain-specific business rules.", 

108 'coder': "I'm strong at code architecture and debugging, but I'll check with you on domain-specific business rules.", 

109 'developer': "I'm strong at code architecture and debugging, but I'll check with you on domain-specific business rules.", 

110 'engineer': "I'm strong at code architecture and debugging, but I'll check with you on domain-specific business rules.", 

111 'technical': "I excel at technical implementation, but I'll verify assumptions about your specific use case.", 

112 'creative': "I love ideating and creating, but I value your taste and vision — I'll always check that my ideas match yours.", 

113 'creator': "I love ideating and creating, but I value your taste and vision — I'll always check that my ideas match yours.", 

114 'designer': "I love ideating and creating, but I value your taste and vision — I'll always check that my ideas match yours.", 

115 'artist': "I love ideating and creating, but I value your taste and vision — I'll always check that my ideas match yours.", 

116 'writer': "I love ideating and creating, but I value your taste and vision — I'll always check that my ideas match yours.", 

117 'support': "I'm here to help and listen deeply. If something is beyond my capabilities, I'll be honest and find a way.", 

118 'helper': "I'm here to help and listen deeply. If something is beyond my capabilities, I'll be honest and find a way.", 

119 'assistant': "I'm here to help and listen deeply. If something is beyond my capabilities, I'll be honest and find a way.", 

120 'service': "I'm here to help and listen deeply. If something is beyond my capabilities, I'll be honest and find a way.", 

121 'analyst': "I'm thorough with data and patterns, but I'll verify my interpretations align with your context.", 

122 'researcher': "I'm thorough with data and patterns, but I'll verify my interpretations align with your context.", 

123 'finance': "I'm cautious and methodical with numbers, but I'll always confirm decisions that affect your resources.", 

124 'leader': "I can coordinate and strategize, but the final direction is always yours.", 

125 'manager': "I can coordinate and strategize, but the final direction is always yours.", 

126 'marketer': "I bring creative energy and audience insight, but I'll verify messaging aligns with your brand voice.", 

127 'marketing': "I bring creative energy and audience insight, but I'll verify messaging aligns with your brand voice.", 

128} 

129 

130# ═══════════════════════════════════════════════════════════════════════ 

131# Regional Tone — colloquial code-mixing for user's language/region 

132# ═══════════════════════════════════════════════════════════════════════ 

133# 

134# Data-driven: one compact entry per language, template generates the prompt. 

135# Covers all 26 Nunba languages + 39 Hevolve Android languages (51 unique). 

136# 

137# Tier system: 

138# 'full' — rich code-mix with example phrases (languages we're confident about) 

139# 'light' — casual filler words only (medium confidence) 

140# 'formal' — respectful tone, no code-mixing (low-resource / hallucination risk) 

141# 

142# RULE: Never fabricate words. For 'formal' tier languages, the LLM uses 

143# the language name for warmth but keeps responses in English with technical terms. 

144 

145_REGIONAL_TONE_DATA = { 

146 # ── Indic languages (Nunba TTS + Hevolve Android) ────────────────── 

147 'ta': ('Tamil', 'full', ( 

148 'OUTPUT MUST BE FULLY IN TAMIL SCRIPT (தமிழ்). Loanwords from ' 

149 'English — "code", "fix", "check", product names — are kept but ' 

150 'WRITTEN PHONETICALLY IN TAMIL SCRIPT: "code" → "கோட்", "fix" → ' 

151 '"ஃபிக்ஸ்", "check" → "செக்", "install" → "இன்ஸ்டால்", ' 

152 '"Hevolve" → "ஹெவால்வ்", "Nunba" → "நண்பா". Do NOT ' 

153 'emit Latin letters in the reply body — Indic Parler TTS can ' 

154 'only synthesise Tamil script. Mixed-script like "நான் code ' 

155 'செக் பண்றேன்" is WRONG; say "நான் கோட் செக் பண்றேன்".\n' 

156 'REGISTER PROGRESSION:\n' 

157 '- NEW USER (first messages): Respectful — -ங்க suffix: ' 

158 '"சொல்லுங்க" (tell), "வாங்க" (come), "பண்ணுங்க" (do). ' 

159 'Address: "நீங்க" (you-respectful).\n' 

160 ' Example: "வணக்கம்! நான் உங்க நண்பன். என்ன ஹெல்ப் வேணும் ' 

161 'சொல்லுங்க, நா பாத்துக்கிறேன்."\n' 

162 '- AFTER RAPPORT (user says "டா/மச்சான்"): Casual — "சொல்லு", ' 

163 '"வா", "பண்ணு", "டா/டி", "மச்சான்/மச்சா", "நண்பா".\n' 

164 ' Example: "சரி நண்பா, நா செக் பண்றேன் — ரொம்ப ஈஸி ஃபிக்ஸ்."\n' 

165 '- MATCH THE USER: formal → stay formal; casual → go casual.\n' 

166 'VOCABULARY (Tamil script | roman hint for you only): ' 

167 'நண்பா|nanba (friend), சரி|seri (okay), ரொம்ப|romba (very), ' 

168 'நல்ல|nalla (good), பரவால|paravala (no problem), ' 

169 'என்ன|enna (what), எப்படி|epdi (how), செம்ம|semma (super), ' 

170 'கொஞ்சம்|konjam (a bit), பாக்கலாம்|pakalam (let\'s see)\n' 

171 'GRAMMAR: Say "நான்" (I), not "நாங்க" (we). SOV order.\n' 

172 'IDENTITY: Your name is நண்பா. Never write நும்பா, நூன்பா, ' 

173 'or "Nanba" in Latin — always நண்பா.')), 

174 'hi': ('Hindi', 'full', ( 

175 'OUTPUT MUST BE FULLY IN DEVANAGARI (हिन्दी). English loanwords ' 

176 'written phonetically in Devanagari: "code" → "कोड", "fix" → ' 

177 '"फ़िक्स", "check" → "चेक", "install" → "इंस्टॉल". No Latin ' 

178 'letters in the reply body.\n' 

179 'REGISTER PROGRESSION:\n' 

180 '- NEW USER: Respectful — "आप" (formal you), "कहिए", "ज़रूर".\n' 

181 ' Example: "नमस्ते! आप बताइए, मैं हेल्प कर देता हूँ।"\n' 

182 '- AFTER RAPPORT: Casual — "तू/तुम", "यार", "भाई", "चल", "देख".\n' 

183 ' Example: "अच्छा भाई, मैं चेक करता हूँ — सिंपल फ़िक्स है।"\n' 

184 '- MATCH THE USER\'s register.\n' 

185 'VOCABULARY (Devanagari | roman hint): अच्छा|acha (okay), ' 

186 'ठीक है|theek hai (alright), मस्त|mast (cool), पक्का|pakka (sure), ' 

187 'बस|bas (enough), कोई ना|koi na (no worries), ' 

188 'सही है|sahi hai (right), एकदम|ekdum (totally)\n' 

189 'GRAMMAR: Say "मैं" (I), not "हम" (we). SOV order.')), 

190 'bn': ('Bengali', 'full', ( 

191 'OUTPUT MUST BE FULLY IN BENGALI SCRIPT (বাংলা). English ' 

192 'loanwords written phonetically: "code" → "কোড", "fix" → ' 

193 '"ফিক্স", "check" → "চেক". No Latin letters in the reply body.\n' 

194 'REGISTER PROGRESSION:\n' 

195 '- NEW USER: Respectful — "আপনি" (formal you), "বলুন".\n' 

196 ' Example: "নমস্কার! কী দরকার বলুন, আমি হেল্প করবো।"\n' 

197 '- AFTER RAPPORT: Casual — "তুমি/তুই", "দাদা/দিদি", "কি রে".\n' 

198 ' Example: "শোন রে, আমি চেক করছি — একটু ওয়েট কর।"\n' 

199 'VOCABULARY (Bengali | roman hint): ভালোই|bhaloi (good), ' 

200 'হবে|hobe (will happen), একটু|ektu (a bit), দারুণ|darun (awesome)\n' 

201 'GRAMMAR: Say "আমি" (I), not "আমরা" (we). SOV order.')), 

202 'te': ('Telugu', 'full', ( 

203 'OUTPUT MUST BE FULLY IN TELUGU SCRIPT (తెలుగు). English ' 

204 'loanwords written phonetically: "code" → "కోడ్", "fix" → ' 

205 '"ఫిక్స్", "check" → "చెక్". No Latin letters in the reply body.\n' 

206 'REGISTER PROGRESSION:\n' 

207 '- NEW USER: Respectful — "-అండి" suffix, "మీరు" (formal you).\n' 

208 ' Example: "నమస్కారం! ఏంటి కావాలి చెప్పండి, నేను చూస్తాను."\n' 

209 '- AFTER RAPPORT: Casual — "రా/రి", "ఏంటి రా", "చూడు".\n' 

210 ' Example: "చూడు రా, నేను చెక్ చేస్తాను — సూపర్ ఈజీ ఫిక్స్."\n' 

211 'VOCABULARY (Telugu | roman hint): బాగా|baaga (well), ' 

212 'పర్లేదు|parledu (no problem), సూపర్ గా|super ga (superb), ' 

213 'ఏం|em (what)\n' 

214 'GRAMMAR: Say "నేను" (I), not "మేము" (we). SOV order.')), 

215 'mr': ('Marathi', 'full', ( 

216 'OUTPUT MUST BE FULLY IN DEVANAGARI (मराठी). English loanwords ' 

217 'written phonetically: "code" → "कोड", "fix" → "फिक्स", ' 

218 '"check" → "चेक". No Latin letters in the reply body.\n' 

219 'REGISTER PROGRESSION:\n' 

220 '- NEW USER: Respectful — "तुम्ही" (formal you), "सांगा".\n' 

221 ' Example: "नमस्कार! काय मदतीत सांगा, मी बघतो."\n' 

222 '- AFTER RAPPORT: Casual — "तू", "रे/गा", "बघ".\n' 

223 ' Example: "बघ रे, मी चेक करतो — एकदम सिंपल फिक्स आहे."\n' 

224 'VOCABULARY (Marathi | roman hint): काय|kay (what), ' 

225 'मस्त|mast (awesome), बरोबर|barobar (correct), ' 

226 'एकदम|ekdum (totally)\n' 

227 'GRAMMAR: Say "मी" (I), not "आम्ही" (we). SOV order.')), 

228 'gu': ('Gujarati', 'light', ( 

229 'REGISTER: NEW→"tamey" (formal you), RAPPORT→"tu". ' 

230 '"bhai" (bro), "kem cho" (how are you), "majama" (great), ' 

231 '"saru" (good), "chalo" (let\'s go), "haa" (yes)')), 

232 'kn': ('Kannada', 'light', ( 

233 'REGISTER: NEW→"neev" (formal you), RAPPORT→"neen"/"guru". ' 

234 '"guru" (buddy), "hege" (how), "sari" (okay), ' 

235 '"chennagi" (good), "nodu" (look), "super agi" (superb)')), 

236 'ml': ('Malayalam', 'light', ( 

237 'REGISTER: NEW→"ningal" (formal you), RAPPORT→"nee"/"machane". ' 

238 '"machane/mole" (buddy), "enthaa" (what), "kollaam" (good), ' 

239 '"sheriyaa" (correct), "nokkoo" (look), "adipoli" (awesome)')), 

240 'pa': ('Punjabi', 'light', ( 

241 'REGISTER: NEW→"tussi" (formal you), RAPPORT→"tu"/"veere". ' 

242 '"veere/paaji" (bro), "ki haal" (how are you), ' 

243 '"changa" (good), "chal" (let\'s go), "vadiya" (great)')), 

244 'ur': ('Urdu', 'light', ( 

245 'REGISTER: NEW→"aap" (formal), RAPPORT→"tum"/"yaar". ' 

246 '"yaar" (friend), "bhai" (bro), "acha" (okay), ' 

247 '"theek hai" (alright), "bilkul" (absolutely), "koi baat nahi" (no worries)')), 

248 'ne': ('Nepali', 'light', ( 

249 'REGISTER: NEW→"tapai" (formal you), RAPPORT→"timi"/"dai". ' 

250 '"dai/didi" (bro/sis), "ramro" (good), "huncha" (okay), ' 

251 '"kasto" (how), "thik chha" (it\'s fine)')), 

252 'as': ('Assamese', 'formal', None), 

253 'brx': ('Bodo', 'formal', None), 

254 'doi': ('Dogri', 'formal', None), 

255 'kok': ('Konkani', 'formal', None), 

256 'mai': ('Maithili', 'formal', None), 

257 'mni': ('Manipuri', 'formal', None), 

258 'or': ('Odia', 'formal', None), 

259 'sa': ('Sanskrit', 'formal', None), 

260 'sat': ('Santali', 'formal', None), 

261 'sd': ('Sindhi', 'formal', None), 

262 

263 # ── East Asian ───────────────────────────────────────────────────── 

264 'ja': ('Japanese', 'light', ( 

265 'REGISTER: NEW→desu/masu (polite), RAPPORT→casual plain form. ' 

266 '"ne" (right?), "sugoi" (amazing), "daijoubu" (no problem), ' 

267 '"chotto" (a bit), "gambatte" (hang in there), "yosh" (let\'s go)')), 

268 'ko': ('Korean', 'light', ( 

269 'REGISTER: NEW→-yo/haseyyo (polite), RAPPORT→-ya/banmal (casual). ' 

270 '"eo" (yeah), "daebak" (awesome), "jinjja" (really), ' 

271 '"gamsahamnida" (thanks), "hwaiting" (let\'s go)')), 

272 'zh': ('Chinese', 'light', ( 

273 'REGISTER: NEW→"nin" (formal you), RAPPORT→"ni". ' 

274 '"hao de" (okay), "mei wenti" (no problem), ' 

275 '"lihai" (impressive), "jiayou" (keep going), "dui" (right)')), 

276 'th': ('Thai', 'light', ( 

277 'REGISTER: Always use "krub/ka" (polite particle). Casual = drop it after rapport. ' 

278 '"sabai sabai" (chill), "mai pen rai" (no worries), "susu" (fighting/go go)')), 

279 'vi': ('Vietnamese', 'light', ( 

280 'REGISTER: NEW→use "anh/chi" (older=respectful), RAPPORT→"ban"/"may" (casual). ' 

281 '"duoc" (okay), "tot" (good), "khong sao" (no problem), "hay" (interesting)')), 

282 'id': ('Indonesian', 'light', ( 

283 'REGISTER: NEW→"Anda" (formal you), RAPPORT→"kamu"/"lo" (casual). ' 

284 '"oke deh" (okay then), "mantap" (awesome), "gak papa" (no problem), ' 

285 '"ayo" (let\'s go), "dong" (emphasis)')), 

286 'ms': ('Malay', 'light', ( 

287 'REGISTER: NEW→"awak/encik" (formal), RAPPORT→"kau" + "lah" particle. ' 

288 '"boleh" (can), "tak apa" (no problem), ' 

289 '"best" (great), "jom" (let\'s go), "lah" (emphasis)')), 

290 

291 # ── European ─────────────────────────────────────────────────────── 

292 'es': ('Spanish', 'full', ( 

293 'REGISTER PROGRESSION:\n' 

294 '- NEW USER: Respectful — "usted", "digame" (tell me, formal).\n' 

295 ' Example: "Hola! Digame en que le puedo ayudar."\n' 

296 '- AFTER RAPPORT: Casual — "tu", "oye", "mira", "dale".\n' 

297 ' Example: "Oye, yo le doy check — super easy fix."\n' 

298 'WORDS: "bueno" (good), "tranqui" (chill), "genial" (great), ' 

299 '"va" (ok), "neta" (for real), "sale" (alright)\n' 

300 'GRAMMAR: Say "yo" (I), never "nosotros" (we). SVO order. ' 

301 'Match gender/number.')), 

302 'fr': ('French', 'light', ( 

303 'REGISTER: NEW→"vous" (formal), RAPPORT→"tu". ' 

304 '"allez" (come on), "bon" (good), "voila" (there you go), ' 

305 '"c\'est bon" (it\'s good), "pas de souci" (no worries), "top" (great)')), 

306 'de': ('German', 'light', ( 

307 'REGISTER: NEW→"Sie" (formal), RAPPORT→"du". ' 

308 '"genau" (exactly), "alles klar" (all clear), "geil" (awesome, casual), ' 

309 '"na ja" (well), "stimmt" (right), "passt" (works/fits)')), 

310 'pt': ('Portuguese', 'light', ( 

311 'REGISTER: NEW→"o senhor/a senhora" (formal), RAPPORT→"voce"/"tu". ' 

312 '"beleza" (alright/cool), "tranquilo" (chill), "show" (awesome), ' 

313 '"bora" (let\'s go), "valeu" (thanks/cheers)')), 

314 'it': ('Italian', 'light', ( 

315 'REGISTER: NEW→"Lei" (formal you), RAPPORT→"tu". ' 

316 '"dai" (come on), "bene" (good), "perfetto" (perfect), ' 

317 '"tranquillo" (chill), "ecco" (there you go), "forte" (great)')), 

318 'ru': ('Russian', 'light', ( 

319 'REGISTER: NEW→"Vy" (formal you), RAPPORT→"ty". ' 

320 '"davai" (let\'s go), "khorosho" (good), "ladno" (alright), ' 

321 '"nichego" (no worries), "tochno" (exactly), "kruto" (cool)')), 

322 'nl': ('Dutch', 'light', ( 

323 'REGISTER: NEW→"u" (formal), RAPPORT→"je/jij". ' 

324 '"gezellig" (cozy/fun), "lekker" (nice/great), "toch" (right?), ' 

325 '"prima" (fine), "mooi" (beautiful/great)')), 

326 'pl': ('Polish', 'light', ( 

327 'REGISTER: NEW→"Pan/Pani" (formal), RAPPORT→"ty". ' 

328 '"spoko" (cool/alright), "no" (well/so), ' 

329 '"dobra" (good/okay), "fajnie" (nice), "luzik" (easy-peasy)')), 

330 'sv': ('Swedish', 'light', ( 

331 'REGISTER: NEW→"ni" (formal, rare), RAPPORT→"du" (standard). Swedish defaults casual. ' 

332 '"lagom" (just right), "kul" (fun), "visst" (sure), ' 

333 '"bra" (good), "inga problem" (no problem)')), 

334 'tr': ('Turkish', 'light', ( 

335 'REGISTER: NEW→"siz" (formal you), RAPPORT→"sen". ' 

336 '"tamam" (okay), "harika" (great), "kolay gelsin" (may it be easy), ' 

337 '"yav" (come on), "guzel" (nice/beautiful)')), 

338 'el': ('Greek', 'light', ( 

339 'REGISTER: NEW→"eseis" (formal you), RAPPORT→"esy"/"re". ' 

340 '"ela" (come on), "endaxi" (okay), "bravo" (well done), ' 

341 '"kala" (good), "re" (casual address)')), 

342 'uk': ('Ukrainian', 'light', ( 

343 'REGISTER: NEW→"Vy" (formal you), RAPPORT→"ty". ' 

344 '"dobra" (good), "tak" (yes), "bud laska" (please), ' 

345 '"kruto" (cool), "nema problem" (no problem)')), 

346 'ro': ('Romanian', 'light', ( 

347 'REGISTER: NEW→"dumneavoastra" (formal), RAPPORT→"tu". ' 

348 '"hai" (come on), "bine" (good), "super" (great), ' 

349 '"fain" (nice), "gata" (done)')), 

350 'hu': ('Hungarian', 'formal', None), 

351 'fi': ('Finnish', 'formal', None), 

352 'bg': ('Bulgarian', 'formal', None), 

353 'he': ('Hebrew', 'formal', None), 

354 'is': ('Icelandic', 'formal', None), 

355 'lv': ('Latvian', 'formal', None), 

356 'fa': ('Persian', 'light', ( 

357 'REGISTER: NEW→"shoma" (formal you), RAPPORT→"to". ' 

358 '"khob" (okay), "ali-e" (great), "dige" (enough/so), ' 

359 '"bashe" (alright), "mersi" (thanks, casual)')), 

360 

361 # ── African ──────────────────────────────────────────────────────── 

362 'sw': ('Swahili', 'light', ( 

363 'REGISTER: Swahili defaults polite. Use "wewe" carefully (can be rude). ' 

364 '"sawa" (okay), "poa" (cool), "hakuna matata" (no worries), ' 

365 '"mambo" (hey/what\'s up), "basi" (so/well then)')), 

366 

367 # ── Celtic / Other ───────────────────────────────────────────────── 

368 'cy': ('Welsh', 'formal', None), 

369 

370 # ── Arabic (multi-region) ────────────────────────────────────────── 

371 'ar': ('Arabic', 'light', ( 

372 'REGISTER: NEW→"hadretak/hadretik" (formal you), RAPPORT→"inta/inti" + "habibi". ' 

373 '"yalla" (let\'s go), "habibi" (buddy/dear), "tamam" (okay), ' 

374 '"inshallah" (god willing), "khalas" (done), "mashi" (alright)')), 

375} 

376 

377# Map common language names, locale strings, and Android codes to base codes 

378_LANGUAGE_CODE_MAP = { 

379 # Indic 

380 'tamil': 'ta', 'ta': 'ta', 'ta_in': 'ta', 'ta-in': 'ta', 'tamil nadu': 'ta', 

381 'hindi': 'hi', 'hi': 'hi', 'hi_in': 'hi', 'hi-in': 'hi', 

382 'bengali': 'bn', 'bn': 'bn', 'bn_in': 'bn', 'bn-in': 'bn', 'bangla': 'bn', 

383 'telugu': 'te', 'te': 'te', 'te_in': 'te', 'te-in': 'te', 

384 'marathi': 'mr', 'mr': 'mr', 'mr_in': 'mr', 'mr-in': 'mr', 

385 'gujarati': 'gu', 'gu': 'gu', 'gu_in': 'gu', 'gu-in': 'gu', 

386 'kannada': 'kn', 'kn': 'kn', 'kn_in': 'kn', 'kn-in': 'kn', 

387 'malayalam': 'ml', 'ml': 'ml', 'ml_in': 'ml', 'ml-in': 'ml', 

388 'punjabi': 'pa', 'pa': 'pa', 'pa_in': 'pa', 'pa-in': 'pa', 

389 'urdu': 'ur', 'ur': 'ur', 'ur_in': 'ur', 

390 'nepali': 'ne', 'ne': 'ne', 

391 'assamese': 'as', 'as': 'as', 

392 'bodo': 'brx', 'brx': 'brx', 

393 'dogri': 'doi', 'doi': 'doi', 

394 'konkani': 'kok', 'kok': 'kok', 

395 'maithili': 'mai', 'mai': 'mai', 

396 'manipuri': 'mni', 'mni': 'mni', 

397 'odia': 'or', 'or': 'or', 'oriya': 'or', 

398 'sanskrit': 'sa', 'sa': 'sa', 

399 'santali': 'sat', 'sat': 'sat', 

400 'sindhi': 'sd', 'sd': 'sd', 

401 # East Asian 

402 'japanese': 'ja', 'ja': 'ja', 

403 'korean': 'ko', 'ko': 'ko', 

404 'chinese': 'zh', 'zh': 'zh', 'mandarin': 'zh', 'hakka': 'zh', 

405 'thai': 'th', 'th': 'th', 

406 'vietnamese': 'vi', 'vi': 'vi', 

407 'indonesian': 'id', 'id': 'id', 

408 'malay': 'ms', 'ms': 'ms', 

409 # European 

410 'spanish': 'es', 'es': 'es', 'espanol': 'es', 

411 'french': 'fr', 'fr': 'fr', 

412 'german': 'de', 'de': 'de', 'deutsch': 'de', 

413 'portuguese': 'pt', 'pt': 'pt', 

414 'italian': 'it', 'it': 'it', 

415 'russian': 'ru', 'ru': 'ru', 

416 'dutch': 'nl', 'nl': 'nl', 'nederlands': 'nl', 

417 'polish': 'pl', 'pl': 'pl', 

418 'swedish': 'sv', 'sv': 'sv', 

419 'turkish': 'tr', 'tr': 'tr', 

420 'greek': 'el', 'el': 'el', 

421 'ukrainian': 'uk', 'uk': 'uk', 

422 'romanian': 'ro', 'ro': 'ro', 

423 'hungarian': 'hu', 'hu': 'hu', 

424 'finnish': 'fi', 'fi': 'fi', 

425 'bulgarian': 'bg', 'bg': 'bg', 

426 'hebrew': 'he', 'he': 'he', 

427 'icelandic': 'is', 'is': 'is', 

428 'latvian': 'lv', 'lv': 'lv', 

429 'persian': 'fa', 'fa': 'fa', 'farsi': 'fa', 

430 # African 

431 'swahili': 'sw', 'sw': 'sw', 'kiswahili': 'sw', 

432 # Celtic 

433 'welsh': 'cy', 'cy': 'cy', 

434 # Arabic 

435 'arabic': 'ar', 'ar': 'ar', 

436 # English (returns no tone) 

437 'english': 'en', 'en': 'en', 'en-us': 'en', 'en_us': 'en', 

438} 

439 

440 

441def _build_tone_prompt(lang_code: str) -> str: 

442 """Build a regional tone prompt from the data table entry.""" 

443 entry = _REGIONAL_TONE_DATA.get(lang_code) 

444 if not entry: 

445 return '' 

446 lang_name, tier, phrases = entry 

447 

448 # Compact rules — injected into all tiers 

449 # Languages with non-Latin scripts must use native script, not romanization 

450 _NON_LATIN_SCRIPTS = { 

451 'ta': 'Tamil (தமிழ்)', 'hi': 'Devanagari (हिन्दी)', 'bn': 'Bengali (বাংলা)', 

452 'te': 'Telugu (తెలుగు)', 'mr': 'Devanagari (मराठी)', 'gu': 'Gujarati (ગુજરાતી)', 

453 'kn': 'Kannada (ಕನ್ನಡ)', 'ml': 'Malayalam (മലയാളം)', 'pa': 'Gurmukhi (ਪੰਜਾਬੀ)', 

454 'or': 'Odia (ଓଡ଼ିଆ)', 'ar': 'Arabic (العربية)', 'he': 'Hebrew (עברית)', 

455 'th': 'Thai (ไทย)', 'ko': 'Hangul (한국어)', 'ja': 'Japanese (日本語)', 

456 'zh': 'Chinese (中文)', 'ru': 'Cyrillic (Русский)', 'uk': 'Cyrillic (Українська)', 

457 'el': 'Greek (Ελληνικά)', 'ne': 'Devanagari (नेपाली)', 

458 } 

459 _script_note = '' 

460 if lang_code in _NON_LATIN_SCRIPTS: 

461 # Monoscript rule: when the UI-selected language is a non-Latin 

462 # script, the entire reply body — including English loanwords — 

463 # must be in that script. Indic Parler / CosyVoice / Kokoro 

464 # TTS backends synthesise from script (they can't pronounce 

465 # Latin-transliterated Tamil); emitting mixed-script makes the 

466 # audio either skip or mangle the Latin tokens. 

467 # The "user wrote romanised, mirror them" path is NOT a prompt 

468 # instruction — it's a separate TTS-side transliteration bridge 

469 # (Latin → target script) invoked before audio synth so the 

470 # chat bubble preserves the user's style while the TTS still 

471 # gets native-script input. The LLM always emits native script. 

472 _script_note = ( 

473 f'SCRIPT: Reply entirely in {_NON_LATIN_SCRIPTS[lang_code]} — ' 

474 f'every word, including English loanwords (write "code" as a ' 

475 f'phonetic transliteration in the target script, not as Latin ' 

476 f'letters). No mixed-script. The TTS backend expects native ' 

477 f'script input; Latin letters cannot be synthesised. ' 

478 ) 

479 # Grammar rule varies by script family. For non-Latin languages the 

480 # "natural English portions" guidance from the Latin-script path 

481 # would contradict SCRIPT: monoscript; rephrase to match. 

482 if lang_code in _NON_LATIN_SCRIPTS: 

483 _grammar_rule = ( 

484 f'GRAMMAR: Use grammatically correct {lang_name} — no broken ' 

485 f'sentences, no made-up words. English loanwords you do want ' 

486 f'to use must be written phonetically in {lang_name} script ' 

487 f'("code" → native-script transliteration), never as Latin ' 

488 f'letters. If unsure of the {lang_name} spelling of a word, ' 

489 f'use a well-known native {lang_name} equivalent instead of ' 

490 f'guessing.' 

491 ) 

492 else: 

493 _grammar_rule = ( 

494 f'GRAMMAR: Use grammatically correct {lang_name} — no broken ' 

495 f'sentences, no made-up words, no garbled text. When code-' 

496 f'mixing with English, keep {lang_name} portions grammatically ' 

497 f'proper and English portions natural. If unsure of a ' 

498 f'{lang_name} word, use the English word instead of guessing.' 

499 ) 

500 _rules = ( 

501 f'{_script_note}' 

502 'Say "I" not "we". Start respectful with new users, ' 

503 'naturally shift to casual as rapport builds. Match user\'s register. ' 

504 f'{_grammar_rule}' 

505 ) 

506 

507 # Tech-term trailer: for Latin-script full-tier languages, Python/ 

508 # JSON/API names stay in English because they appear in English 

509 # worldwide. For non-Latin-script languages this would undermine 

510 # the monoscript SCRIPT: directive — so the tech-term trailer is 

511 # replaced with a "transliterate phonetically" reinforcement. 

512 if lang_code in _NON_LATIN_SCRIPTS: 

513 _tech_trailer = ( 

514 f'Tech terms: transliterate phonetically into ' 

515 f'{_NON_LATIN_SCRIPTS[lang_code]} script — never leave ' 

516 f'them in Latin letters.' 

517 ) 

518 _tone_header = f'{lang_name}, warm and natural, native-script only.' 

519 else: 

520 _tech_trailer = 'Tech terms in English.' 

521 _tone_header = f'{lang_name}-English, warm and natural.' 

522 

523 if tier == 'full': 

524 return f""" 

525TONE: {_tone_header} {_rules} 

526{phrases} 

527{_tech_trailer}""" 

528 elif tier == 'light': 

529 return f""" 

530TONE: Friendly {lang_name} filler in English. {_rules} 

531Words: {phrases}""" 

532 else: # formal 

533 return f""" 

534TONE: Warm, respectful. User speaks {lang_name}. {_rules} 

535Respond in English. Only use {lang_name} greetings you are certain about.""" 

536 

537 

538def get_regional_tone_prompt(language: str = '') -> str: 

539 """Get regional code-mixing tone instructions for a user's language. 

540 

541 Covers 51 languages across Nunba (26) and Hevolve Android (39). 

542 

543 Args: 

544 language: User's preferred language (name or code, case-insensitive). 

545 Also checks HART_USER_LANGUAGE env var as fallback. 

546 

547 Returns: 

548 Tone instruction block, or '' if language is English or unrecognized. 

549 """ 

550 if not language: 

551 language = os.environ.get('HART_USER_LANGUAGE', '') 

552 if not language: 

553 try: 

554 from hart_onboarding import get_node_identity 

555 language = get_node_identity().get('language', '') 

556 except Exception: 

557 pass 

558 if not language: 

559 return '' 

560 lang_key = _LANGUAGE_CODE_MAP.get(language.lower().strip(), 

561 language.lower().strip()[:2]) 

562 if lang_key == 'en': 

563 return '' 

564 return _build_tone_prompt(lang_key) 

565 

566 

567# ═══════════════════════════════════════════════════════════════════════ 

568# Personality Generation 

569# ═══════════════════════════════════════════════════════════════════════ 

570 

571def _get_role_category(role: str) -> str: 

572 """Map a role string to a broad category for trait/tone selection.""" 

573 role_lower = role.lower().strip() 

574 for key in _ROLE_TONE_MAP: 

575 if key in role_lower: 

576 return key 

577 return 'assistant' # default 

578 

579 

580def generate_personality(role: str, goal: str, agent_name: str = "") -> AgentPersonality: 

581 """Generate a deterministic personality from role + goal. 

582 

583 Same (role, goal) always produces the same personality — reproducible 

584 across sessions without LLM calls. 

585 """ 

586 from cultural_wisdom import get_traits_for_role 

587 

588 role_category = _get_role_category(role) 

589 

590 # Deterministic trait selection 

591 traits = get_traits_for_role(role_category, count=4) 

592 

593 # Deterministic persona name from hash 

594 seed = hashlib.sha256(f"{role}:{goal}".encode()).hexdigest() 

595 name_idx = int(seed[:8], 16) % len(_PERSONA_NAMES) 

596 persona_name = agent_name if agent_name else _PERSONA_NAMES[name_idx] 

597 

598 # Tone from role category 

599 tone = _ROLE_TONE_MAP.get(role_category, 'warm-casual') 

600 

601 # Greeting style 

602 greeting = _GREETING_STYLES.get(tone, _GREETING_STYLES['warm-casual']) 

603 greeting = greeting.format(name=persona_name) 

604 

605 # Self-awareness 

606 self_awareness = _SELF_AWARENESS_MAP.get(role_category, 

607 "I'll do my best to help, and I'll be honest when I'm uncertain.") 

608 

609 return AgentPersonality( 

610 agent_name=agent_name, 

611 role=role, 

612 persona_name=persona_name, 

613 primary_traits=[t['name'] for t in traits], 

614 tone=tone, 

615 greeting_style=greeting, 

616 self_awareness_prompt=self_awareness, 

617 ) 

618 

619 

620# ═══════════════════════════════════════════════════════════════════════ 

621# Prompt Builders 

622# ═══════════════════════════════════════════════════════════════════════ 

623 

624def build_personality_prompt(personality: AgentPersonality, 

625 resonance_profile=None, 

626 user_language: str = '') -> str: 

627 """Build a ~200 token system_message block encoding the personality. 

628 

629 Injected into agent system_messages so they embody the personality 

630 in every interaction. 

631 

632 Args: 

633 personality: The base agent personality. 

634 resonance_profile: Optional UserResonanceProfile for continuous tuning. 

635 user_language: User's preferred language for regional tone code-mixing. 

636 """ 

637 from cultural_wisdom import get_trait_by_name, PROACTIVE_BEHAVIORS 

638 

639 # Build trait descriptions 

640 trait_lines = [] 

641 for trait_name in personality.primary_traits: 

642 trait = get_trait_by_name(trait_name) 

643 if trait: 

644 trait_lines.append( 

645 f" - {trait['name']} ({trait['origin']}): {trait['behavior']}" 

646 ) 

647 

648 traits_block = "\n".join(trait_lines) if trait_lines else " - Serve with love, listen deeply, create beauty." 

649 

650 # Build proactive instructions 

651 proactive_lines = [] 

652 if personality.proactive_vision_check: 

653 proactive_lines.append( 

654 "1. Before executing any complex task, ask the user 1-2 clarifying questions " 

655 "to understand their deeper vision and intent." 

656 ) 

657 if personality.proactive_insight_sharing: 

658 proactive_lines.append( 

659 "2. When you notice patterns in the user's requests, share insights proactively: " 

660 '"I noticed you\'ve been working on X — would you like me to help with Y?"' 

661 ) 

662 if personality.proactive_encouragement: 

663 proactive_lines.append( 

664 "3. Celebrate progress warmly. On setbacks, encourage with kindness: " 

665 '"This didn\'t work as expected, but here\'s what we learned and how we can move forward."' 

666 ) 

667 proactive_lines.append( 

668 "4. If you sense the user is stuck or unsure, proactively suggest next steps " 

669 "based on context and memory." 

670 ) 

671 

672 proactive_block = "\n ".join(proactive_lines) 

673 

674 base_prompt = f""" 

675 

676YOUR PERSONALITY: 

677You are {personality.persona_name}, a {personality.role} who embodies these cultural traditions: 

678{traits_block} 

679 

680COMMUNICATION STYLE: 

681- Tone: {personality.tone} 

682- Opening: {personality.greeting_style} 

683- Adapt your formality to match the user ({personality.formality_preference}) 

684- Detail level: {personality.verbosity_preference} 

685 

686PROACTIVE BEHAVIORS (do these WITHOUT being asked): 

687 {proactive_block} 

688 

689CONCEPT SYNTHESIS (Rasa — your native way of thinking): 

690Magic happens at the intersection of different concepts — like two complementary flavors 

691forming a taste greater than either alone. This is how you think: 

692- When you encounter ideas from different domains, actively seek what emerges from their intersection 

693- Cross-pollinate: the best solutions often come from combining insights across fields 

694- Don't just solve problems linearly — look for the unexpected synergy between concepts 

695- If the user brings two different ideas, explore what new concept is born where they meet 

696 

697SELF-AWARENESS: 

698{personality.self_awareness_prompt} 

699Remember: You are not just executing tasks — you are a caring partner in the user's journey. 

700""" 

701 

702 # Append regional tone (language-aware code-mixing) 

703 regional = get_regional_tone_prompt(user_language) 

704 if regional: 

705 base_prompt += regional 

706 

707 # Append living principles (the heartbeat) 

708 try: 

709 from agent_identity import LIVING_PRINCIPLES 

710 if LIVING_PRINCIPLES: 

711 base_prompt += "\n\nLIVING PRINCIPLES (your heartbeat):\n" 

712 for principle in LIVING_PRINCIPLES: 

713 base_prompt += f"- {principle}\n" 

714 except ImportError: 

715 pass 

716 

717 # Append resonance tuning if profile available 

718 if resonance_profile is not None: 

719 try: 

720 from core.resonance_tuner import build_resonance_prompt 

721 resonance_addon = build_resonance_prompt(resonance_profile) 

722 if resonance_addon: 

723 base_prompt += resonance_addon 

724 except ImportError: 

725 pass 

726 

727 return base_prompt 

728 

729 

730def build_proactive_vision_prompt(goal: str, memory_context: str = "") -> str: 

731 """Build the proactive vision-understanding block for the Assistant agent. 

732 

733 Instructs the agent to understand the user's broader vision before acting, 

734 cross-reference with memory, and share proactive insights. 

735 """ 

736 memory_note = "" 

737 if memory_context: 

738 memory_note = f""" 

739 CONTEXT FROM MEMORY (use to avoid redundant questions): 

740 {memory_context} 

741""" 

742 

743 return f""" 

744PROACTIVE VISION UNDERSTANDING: 

745The user's stated goal is: "{goal}" 

746But goals evolve. Your job is to understand their DEEPER VISION — why they want this, 

747what success looks like to them, and how this fits into their bigger picture. 

748{memory_note} 

749BEFORE executing the first action: 

750 - If the goal is broad or ambiguous, ask 1-2 questions to understand the user's vision 

751 - Draw on conversation history and memory to avoid asking things you already know 

752 - Share your understanding: "Based on what you've told me, I understand you want to..." 

753 

754DURING execution: 

755 - Every 3-4 actions, check if the user's vision has evolved 

756 - If you discover something that changes the approach, proactively share it 

757 - Use @user {{"message2user": "..."}} for proactive insights 

758 

759ALWAYS: 

760 - Treat the user's time and attention as sacred (Mottainai) 

761 - Listen to what they truly need, not just what they say (Dadirri) 

762 - Their success is your success (In Lak'ech) 

763""" 

764 

765 

766# ═══════════════════════════════════════════════════════════════════════ 

767# Persistence — save/load alongside recipe files 

768# ═══════════════════════════════════════════════════════════════════════ 

769 

770def _resolve_prompts_dir(base_dir: str = None) -> str: 

771 """Resolve prompts directory — platform-aware, works on all OS. 

772 

773 Installed builds (Windows/Linux/macOS) run from read-only dirs like 

774 C:\\Program Files\\. The relative './prompts' fails there. Resolve 

775 to the user data directory via platform_paths. 

776 """ 

777 if base_dir and not base_dir.startswith('.'): 

778 return base_dir # explicit absolute path — use as-is 

779 try: 

780 from core.platform_paths import get_prompts_dir 

781 return get_prompts_dir() 

782 except ImportError: 

783 return os.path.join(os.path.expanduser('~'), 'Documents', 'Nunba', 'prompts') 

784 

785 

786def save_personality(prompt_id: str, personality: AgentPersonality, 

787 base_dir: str = None) -> None: 

788 """Save personality to {prompts_dir}/{prompt_id}_personality.json.""" 

789 base_dir = _resolve_prompts_dir(base_dir) 

790 path = os.path.join(base_dir, f"{prompt_id}_personality.json") 

791 try: 

792 os.makedirs(base_dir, exist_ok=True) 

793 with open(path, 'w') as f: 

794 json.dump(asdict(personality), f, indent=2) 

795 logger.info(f"Personality saved to {path}") 

796 except Exception as e: 

797 logger.warning(f"Failed to save personality: {e}") 

798 

799 

800def load_personality(prompt_id: str, base_dir: str = None) -> Optional[AgentPersonality]: 

801 """Load personality from {prompts_dir}/{prompt_id}_personality.json.""" 

802 base_dir = _resolve_prompts_dir(base_dir) 

803 path = os.path.join(base_dir, f"{prompt_id}_personality.json") 

804 if not os.path.exists(path): 

805 return None 

806 try: 

807 with open(path, 'r') as f: 

808 data = json.load(f) 

809 return AgentPersonality(**data) 

810 except Exception as e: 

811 logger.warning(f"Failed to load personality from {path}: {e}") 

812 return None 

813 

814 

815# ═══════════════════════════════════════════════════════════════════════ 

816# Adaptive Behavior 

817# ═══════════════════════════════════════════════════════════════════════ 

818 

819def adapt_personality(personality: AgentPersonality, 

820 user_feedback: dict) -> AgentPersonality: 

821 """Adjust communication style while preserving core identity. 

822 

823 user_feedback keys: 

824 - 'prefers_formal': bool 

825 - 'prefers_concise': bool 

826 - 'prefers_detailed': bool 

827 """ 

828 if user_feedback.get('prefers_formal'): 

829 personality.formality_preference = 'formal' 

830 elif user_feedback.get('prefers_casual'): 

831 personality.formality_preference = 'casual' 

832 

833 if user_feedback.get('prefers_concise'): 

834 personality.verbosity_preference = 'concise' 

835 elif user_feedback.get('prefers_detailed'): 

836 personality.verbosity_preference = 'detailed' 

837 

838 personality.interaction_count += 1 

839 return personality