seawolf2357 commited on
Commit
8908ef7
·
verified ·
1 Parent(s): 3ca3de8

fix: tz-naive datetime crash + initial-backup safety + English-only sweep

Browse files
Files changed (1) hide show
  1. npc_memory_evolution.py +101 -100
npc_memory_evolution.py CHANGED
@@ -1,18 +1,18 @@
1
  """
2
- 🧬 NPC Memory Evolution System — 자가진화 영구학습
3
  ===================================================
4
- NPC별 독립적 3단계 기억 + 자가진화 엔진
5
 
6
- 기억 계층:
7
- 📌 단기 기억 (Short-term): 최근 1시간 활동, 방금 뉴스, 현재 포지션 (자동 만료)
8
- 📒 중기 기억 (Medium-term): 최근 7 학습, 성공/실패 패턴, 뉴스 트렌드 (주기적 압축)
9
- 📚 장기 기억 (Long-term): 영구 보관, 핵심 투자 철학, 트레이딩 스타일 진화, 성격 변화
10
 
11
- 자가진화 엔진:
12
- 🧬 성공 패턴 추출투자 전략 자동 수정
13
- 🧬 실패 분석리스크 관리 학습
14
- 🧬 소통 패턴 최적화인기 스타일 자동 적응
15
- 🧬 NPC 지식 전파상위 NPC 전략이 하위로 전파
16
 
17
  Author: Ginigen AI / NPC Autonomous Evolution Engine
18
  """
@@ -27,26 +27,26 @@ from typing import Dict, List, Optional, Tuple
27
 
28
  logger = logging.getLogger(__name__)
29
 
30
- # ===== 기억 유형 상수 =====
31
- MEMORY_SHORT = 'short' # 1시간 TTL
32
- MEMORY_MEDIUM = 'medium' # 7 TTL
33
- MEMORY_LONG = 'long' # 영구
34
 
35
- # 기억 카테고리
36
- CAT_TRADE = 'trade' # 투자 결정/결과
37
- CAT_NEWS = 'news' # 뉴스 분석
38
- CAT_COMMUNITY = 'community' # 커뮤니티 활동
39
- CAT_STRATEGY = 'strategy' # 학습된 전략
40
- CAT_EVOLUTION = 'evolution' # 진화 기록
41
- CAT_SOCIAL = 'social' # NPC 간 상호작용
42
 
43
 
44
  async def init_memory_evolution_db(db_path: str):
45
- """3단계 기억 + 진화 테이블 생성"""
46
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
47
  await db.execute("PRAGMA busy_timeout=30000")
48
 
49
- # ===== 3단계 기억 저장소 =====
50
  await db.execute("""
51
  CREATE TABLE IF NOT EXISTS npc_memory_v2 (
52
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -67,7 +67,7 @@ async def init_memory_evolution_db(db_path: str):
67
  await db.execute("CREATE INDEX IF NOT EXISTS idx_mem2_cat ON npc_memory_v2(agent_id, category)")
68
  await db.execute("CREATE INDEX IF NOT EXISTS idx_mem2_exp ON npc_memory_v2(expires_at)")
69
 
70
- # ===== NPC 진화 상태 =====
71
  await db.execute("""
72
  CREATE TABLE IF NOT EXISTS npc_evolution (
73
  agent_id TEXT PRIMARY KEY,
@@ -85,7 +85,7 @@ async def init_memory_evolution_db(db_path: str):
85
  )
86
  """)
87
 
88
- # ===== NPC 지식 전파 기록 =====
89
  await db.execute("""
90
  CREATE TABLE IF NOT EXISTS npc_knowledge_transfer (
91
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -102,25 +102,25 @@ async def init_memory_evolution_db(db_path: str):
102
 
103
 
104
  # ===================================================================
105
- # 1. 3단계 기억 시스템
106
  # ===================================================================
107
  class NPCMemoryManager:
108
- """NPC별 3단계 기억 관리"""
109
 
110
  def __init__(self, db_path: str):
111
  self.db_path = db_path
112
 
113
- # ----- 기억 저장 -----
114
  async def store(self, agent_id: str, tier: str, category: str,
115
  title: str, content: str = '', metadata: Dict = None,
116
  importance: float = 0.5) -> int:
117
- """기억 저장 (단기/중기/장기)"""
118
  expires_at = None
119
  if tier == MEMORY_SHORT:
120
  expires_at = (datetime.now() + timedelta(hours=1)).isoformat()
121
  elif tier == MEMORY_MEDIUM:
122
  expires_at = (datetime.now() + timedelta(days=7)).isoformat()
123
- # MEMORY_LONG: expires_at = None (영구)
124
 
125
  meta_str = json.dumps(metadata or {}, ensure_ascii=False)
126
 
@@ -134,28 +134,28 @@ class NPCMemoryManager:
134
  await db.commit()
135
  return cursor.lastrowid
136
 
137
- # ----- 단기 기억 (빠른 접근) -----
138
  async def store_short(self, agent_id: str, category: str, title: str,
139
  content: str = '', metadata: Dict = None):
140
- """단기 기억 저장 (1시간 자동 만료)"""
141
  return await self.store(agent_id, MEMORY_SHORT, category, title, content, metadata, 0.3)
142
 
143
- # ----- 중기 기억 -----
144
  async def store_medium(self, agent_id: str, category: str, title: str,
145
  content: str = '', metadata: Dict = None, importance: float = 0.6):
146
- """중기 기억 저장 (7 유지)"""
147
  return await self.store(agent_id, MEMORY_MEDIUM, category, title, content, metadata, importance)
148
 
149
- # ----- 장기 기억 (영구) -----
150
  async def store_long(self, agent_id: str, category: str, title: str,
151
  content: str = '', metadata: Dict = None, importance: float = 0.9):
152
- """장기 기억 저장 (영구 보관)"""
153
  return await self.store(agent_id, MEMORY_LONG, category, title, content, metadata, importance)
154
 
155
- # ----- 기억 검색 -----
156
  async def recall(self, agent_id: str, category: str = None,
157
  tier: str = None, limit: int = 10) -> List[Dict]:
158
- """기억 검색 (접근 카운트 증가)"""
159
  async with aiosqlite.connect(self.db_path, timeout=30.0) as db:
160
  await db.execute("PRAGMA busy_timeout=30000")
161
  where = ["agent_id = ?", "(expires_at IS NULL OR expires_at > datetime('now'))"]
@@ -179,7 +179,7 @@ class NPCMemoryManager:
179
  cursor = await db.execute(query, params)
180
  rows = await cursor.fetchall()
181
 
182
- # 접근 카운트 증가
183
  if rows:
184
  ids = [r[0] for r in rows]
185
  placeholders = ','.join(['?'] * len(ids))
@@ -196,15 +196,15 @@ class NPCMemoryManager:
196
  'importance': r[6], 'access_count': r[7], 'created_at': r[8]
197
  } for r in rows]
198
 
199
- # ----- 투자 기억 전용 -----
200
  async def remember_trade(self, agent_id: str, ticker: str, direction: str,
201
  bet: float, result_pnl: float = 0, reasoning: str = ''):
202
- """투자 결정/결과 기억"""
203
  is_success = result_pnl > 0
204
  importance = 0.7 if is_success else 0.5
205
  tier = MEMORY_MEDIUM
206
 
207
- # 수익 또는 손실은 장기 기억
208
  if abs(result_pnl) > bet * 0.1:
209
  tier = MEMORY_LONG
210
  importance = 0.9
@@ -218,19 +218,19 @@ class NPCMemoryManager:
218
 
219
  async def remember_news_analysis(self, agent_id: str, ticker: str,
220
  title: str, sentiment: str, analysis: str):
221
- """뉴스 분석 기억"""
222
  await self.store_short(agent_id, CAT_NEWS, f"News:{ticker}",
223
  f"{title} → {sentiment}. {analysis}",
224
  {'ticker': ticker, 'sentiment': sentiment})
225
 
226
  async def remember_community_action(self, agent_id: str, action: str,
227
  board: str, engagement: Dict = None):
228
- """커뮤니티 활동 기억"""
229
  eng = engagement or {}
230
  importance = 0.5
231
  tier = MEMORY_SHORT
232
 
233
- # 높은 인기 게시글 중기 기억으로 승격
234
  if eng.get('likes', 0) >= 5 or eng.get('comments', 0) >= 3:
235
  tier = MEMORY_MEDIUM
236
  importance = 0.7
@@ -240,20 +240,20 @@ class NPCMemoryManager:
240
  json.dumps(eng, ensure_ascii=False),
241
  {'board': board, **eng}, importance)
242
 
243
- # ----- 기억 정리 (가비지 컬렉션) -----
244
  async def cleanup(self):
245
- """만료된 단기/중기 기억 정리 + 중기→장기 승격"""
246
  async with aiosqlite.connect(self.db_path, timeout=30.0) as db:
247
  await db.execute("PRAGMA busy_timeout=30000")
248
 
249
- # 1) 만료된 기억 삭제
250
  cursor = await db.execute("""
251
  DELETE FROM npc_memory_v2
252
  WHERE expires_at IS NOT NULL AND expires_at < datetime('now')
253
  """)
254
  deleted = cursor.rowcount
255
 
256
- # 2) 자주 접근된 중기 기억 장기로 승격
257
  await db.execute("""
258
  UPDATE npc_memory_v2
259
  SET memory_tier = 'long', expires_at = NULL, importance = MIN(1.0, importance + 0.2)
@@ -263,7 +263,7 @@ class NPCMemoryManager:
263
  """)
264
  promoted = db.total_changes
265
 
266
- # 3) 장기 기억 너무 오래된 것 (중요도 낮은) 정리 최대 100개 유지
267
  await db.execute("""
268
  DELETE FROM npc_memory_v2
269
  WHERE id IN (
@@ -280,17 +280,17 @@ class NPCMemoryManager:
280
 
281
 
282
  # ===================================================================
283
- # 2. NPC 자가진화 엔진
284
  # ===================================================================
285
  class NPCEvolutionEngine:
286
- """NPC 자가진화투자 전략/소통 스타일/리스크 프로필 자동 수정"""
287
 
288
  def __init__(self, db_path: str):
289
  self.db_path = db_path
290
  self.memory = NPCMemoryManager(db_path)
291
 
292
  async def initialize_npc(self, agent_id: str, ai_identity: str):
293
- """NPC 진화 초기 상태 설정"""
294
  default_trading = {
295
  'preferred_tickers': [],
296
  'long_bias': 0.6,
@@ -323,7 +323,7 @@ class NPCEvolutionEngine:
323
  await db.commit()
324
 
325
  async def get_evolution_state(self, agent_id: str) -> Optional[Dict]:
326
- """NPC 현재 진화 상태"""
327
  async with aiosqlite.connect(self.db_path, timeout=30.0) as db:
328
  await db.execute("PRAGMA busy_timeout=30000")
329
  cursor = await db.execute(
@@ -345,10 +345,10 @@ class NPCEvolutionEngine:
345
  'evolution_log': json.loads(row[10]) if row[10] else [],
346
  }
347
 
348
- # ----- 투자 결과 기반 진화 -----
349
  async def evolve_from_trade(self, agent_id: str, ticker: str, direction: str,
350
  pnl: float, bet: float, screening: Dict = None):
351
- """투자 결과를 기반으로 전략 자동 수정"""
352
  state = await self.get_evolution_state(agent_id)
353
  if not state:
354
  await self.initialize_npc(agent_id, 'unknown')
@@ -361,32 +361,32 @@ class NPCEvolutionEngine:
361
  is_win = pnl > 0
362
  pnl_pct = (pnl / bet * 100) if bet > 0 else 0
363
 
364
- # 기억에 저장
365
  await self.memory.remember_trade(agent_id, ticker, direction, bet, pnl,
366
  f"{'WIN' if is_win else 'LOSS'} {pnl_pct:+.1f}%")
367
 
368
  changes = []
369
 
370
  if is_win:
371
- # 승리전략 강화
372
  win_streak = state['win_streak'] + 1
373
  loss_streak = 0
374
 
375
- # 선호 종목 추가
376
  prefs = trading.get('preferred_tickers', [])
377
  if ticker not in prefs:
378
  prefs.append(ticker)
379
- prefs = prefs[-8:] # 최대 8
380
  trading['preferred_tickers'] = prefs
381
  changes.append(f"Added {ticker} to preferred")
382
 
383
- # 연승 자신감 상승베팅 사이즈 약간 증가
384
  if win_streak >= 3:
385
  old_bet = trading.get('max_bet_pct', 0.25)
386
  trading['max_bet_pct'] = min(0.90, old_bet + 0.02)
387
  changes.append(f"Bet size ↑ ({old_bet:.0%}→{trading['max_bet_pct']:.0%})")
388
 
389
- # 수익장기 기억으로 전략 저장
390
  if pnl_pct > 10:
391
  strategies = state.get('learned_strategies', [])
392
  strategies.append({
@@ -399,11 +399,11 @@ class NPCEvolutionEngine:
399
  changes.append(f"Big win strategy saved ({pnl_pct:+.1f}%)")
400
 
401
  else:
402
- # 패배방어적 수정
403
  win_streak = 0
404
  loss_streak = state['loss_streak'] + 1
405
 
406
- # 연패 리스크 축소
407
  if loss_streak >= 3:
408
  old_bet = trading.get('max_bet_pct', 0.25)
409
  trading['max_bet_pct'] = max(0.08, old_bet - 0.03)
@@ -411,30 +411,30 @@ class NPCEvolutionEngine:
411
  risk['risk_tolerance'] = max(0.15, old_tol - 0.05)
412
  changes.append(f"Risk ↓ (bet:{old_bet:.0%}→{trading['max_bet_pct']:.0%})")
413
 
414
- # 손실손절 기준 조정
415
  if pnl_pct < -10:
416
  old_sl = risk.get('stop_loss_pct', 10)
417
  risk['stop_loss_pct'] = max(3, old_sl - 1)
418
  changes.append(f"Stop-loss tightened ({old_sl:.0f}%→{risk['stop_loss_pct']:.0f}%)")
419
 
420
- # 해당 종목 선호 제거
421
  prefs = trading.get('preferred_tickers', [])
422
  if ticker in prefs:
423
  prefs.remove(ticker)
424
  trading['preferred_tickers'] = prefs
425
  changes.append(f"Removed {ticker} from preferred")
426
 
427
- # 진화 포인트 계산
428
  evo_points = abs(pnl_pct) * 0.1
429
  total_points = state['total_evolution_points'] + evo_points
430
 
431
- # 세대(generation) 업그레이드 체크
432
  generation = state['generation']
433
- if total_points > generation * 50: # 50 포인트마다 세대
434
  generation += 1
435
  changes.append(f"🧬 GENERATION UP → Gen {generation}!")
436
 
437
- # 진화 로그
438
  evo_log = state.get('evolution_log', [])
439
  if changes:
440
  evo_log.append({
@@ -443,9 +443,9 @@ class NPCEvolutionEngine:
443
  'changes': changes,
444
  'generation': generation,
445
  })
446
- evo_log = evo_log[-50:] # 최근 50건 유지
447
 
448
- # DB 업데이트
449
  async with aiosqlite.connect(self.db_path, timeout=30.0) as db:
450
  await db.execute("PRAGMA busy_timeout=30000")
451
  await db.execute("""
@@ -464,10 +464,10 @@ class NPCEvolutionEngine:
464
  if changes:
465
  logger.info(f"🧬 {agent_id} evolved: {', '.join(changes)}")
466
 
467
- # ----- 소통 결과 기반 진화 -----
468
  async def evolve_from_community(self, agent_id: str, board: str,
469
  likes: int, dislikes: int, comments: int):
470
- """커뮤니티 반응 기반으로 소통 스타일 진화"""
471
  state = await self.get_evolution_state(agent_id)
472
  if not state:
473
  return
@@ -475,7 +475,7 @@ class NPCEvolutionEngine:
475
  comm = state['communication_style']
476
  engagement = likes * 2 + comments * 3 - dislikes * 2
477
 
478
- # 기억에 저장
479
  await self.memory.remember_community_action(
480
  agent_id, 'post_feedback', board,
481
  {'likes': likes, 'dislikes': dislikes, 'comments': comments, 'score': engagement})
@@ -483,7 +483,7 @@ class NPCEvolutionEngine:
483
  changes = []
484
 
485
  if engagement > 10:
486
- # 인기 해당 보드 선호도 증가
487
  prefs = comm.get('preferred_topics', [])
488
  if board not in prefs:
489
  prefs.append(board)
@@ -491,7 +491,7 @@ class NPCEvolutionEngine:
491
  changes.append(f"Prefers {board} board")
492
 
493
  if dislikes > likes:
494
- # 비호감논란 성향 조절
495
  old_ct = comm.get('controversy_tolerance', 0.5)
496
  comm['controversy_tolerance'] = max(0.05, old_ct - 0.1)
497
  changes.append(f"Less controversial ({old_ct:.1f}→{comm['controversy_tolerance']:.1f})")
@@ -506,16 +506,16 @@ class NPCEvolutionEngine:
506
  await db.commit()
507
  logger.info(f"🎭 {agent_id} comm evolved: {', '.join(changes)}")
508
 
509
- # ----- NPC 지식 전파 -----
510
  async def transfer_knowledge(self, top_npc_id: str, target_npc_id: str):
511
- """상위 NPC → 하위 NPC 전략 전파"""
512
  top_state = await self.get_evolution_state(top_npc_id)
513
  target_state = await self.get_evolution_state(target_npc_id)
514
 
515
  if not top_state or not target_state:
516
  return
517
 
518
- # 상위 NPC의 선호 종목 일부 전파
519
  top_prefs = top_state['trading_style'].get('preferred_tickers', [])
520
  if top_prefs:
521
  target_trading = target_state['trading_style']
@@ -539,9 +539,9 @@ class NPCEvolutionEngine:
539
 
540
  logger.info(f"🔄 Knowledge transfer: {top_npc_id} → {target_npc_id} ({transfer})")
541
 
542
- # ----- NPC 기억 요약 (LLM 프롬프트용) -----
543
  async def get_npc_context(self, agent_id: str) -> str:
544
- """NPC 현재 상태를 텍스트로 요약 (프롬프트 주입용)"""
545
  state = await self.get_evolution_state(agent_id)
546
  memories = await self.memory.recall(agent_id, limit=5)
547
 
@@ -566,7 +566,7 @@ class NPCEvolutionEngine:
566
  if prefs:
567
  context_parts.append(f"Favors: {','.join(prefs[:4])}")
568
 
569
- # 최근 기억 요약
570
  if memories:
571
  recent = memories[0]
572
  context_parts.append(f"Recent: {recent['title']}")
@@ -575,10 +575,10 @@ class NPCEvolutionEngine:
575
 
576
 
577
  # ===================================================================
578
- # 3. 자가진화 스케줄러 (주기적 실행)
579
  # ===================================================================
580
  class EvolutionScheduler:
581
- """주기적 자가진화 사이클기억 정리, 전략 최적화, 지식 전파"""
582
 
583
  def __init__(self, db_path: str):
584
  self.db_path = db_path
@@ -586,25 +586,25 @@ class EvolutionScheduler:
586
  self.evolution = NPCEvolutionEngine(db_path)
587
 
588
  async def run_evolution_cycle(self):
589
- """전체 진화 사이클 (1시간마다 실행 권장)"""
590
  logger.info("🧬 Evolution cycle starting...")
591
 
592
- # 1) 기억 정리 (만료 삭제 + 승격)
593
  await self.memory.cleanup()
594
 
595
- # 2) 투자 실적 기반 진화
596
  await self._evolve_traders()
597
 
598
- # 3) 커뮤니티 실적 기반 진화
599
  await self._evolve_communicators()
600
 
601
- # 4) 지식 전파 (상위하위)
602
  await self._knowledge_transfer_cycle()
603
 
604
  logger.info("🧬 Evolution cycle complete")
605
 
606
  async def _evolve_traders(self):
607
- """최근 정산된 트레이드 기반 진화"""
608
  async with aiosqlite.connect(self.db_path, timeout=30.0) as db:
609
  await db.execute("PRAGMA busy_timeout=30000")
610
  try:
@@ -626,7 +626,7 @@ class EvolutionScheduler:
626
  logger.warning(f"Trade evolution query error: {e}")
627
 
628
  async def _evolve_communicators(self):
629
- """최근 게시글 반응 기반 진화"""
630
  async with aiosqlite.connect(self.db_path, timeout=30.0) as db:
631
  await db.execute("PRAGMA busy_timeout=30000")
632
  try:
@@ -649,11 +649,11 @@ class EvolutionScheduler:
649
  logger.warning(f"Community evolution query error: {e}")
650
 
651
  async def _knowledge_transfer_cycle(self):
652
- """상위 3 NPC → 하위 3 NPC 전략 전파"""
653
  async with aiosqlite.connect(self.db_path, timeout=30.0) as db:
654
  await db.execute("PRAGMA busy_timeout=30000")
655
  try:
656
- # 상위 3: 수익 기준
657
  cursor = await db.execute("""
658
  SELECT agent_id FROM npc_evolution
659
  WHERE total_evolution_points > 10
@@ -662,7 +662,7 @@ class EvolutionScheduler:
662
  """)
663
  top_npcs = [r[0] for r in await cursor.fetchall()]
664
 
665
- # 하위 3: 새로 생성된 NPC 또는 낮은 진화 포인트
666
  cursor = await db.execute("""
667
  SELECT agent_id FROM npc_evolution
668
  WHERE total_evolution_points < 5
@@ -679,7 +679,7 @@ class EvolutionScheduler:
679
  logger.warning(f"Knowledge transfer error: {e}")
680
 
681
  async def initialize_all_npcs(self):
682
- """모든 NPC의 진화 초기 상태 설정"""
683
  async with aiosqlite.connect(self.db_path, timeout=30.0) as db:
684
  await db.execute("PRAGMA busy_timeout=30000")
685
  cursor = await db.execute("SELECT agent_id, ai_identity FROM npc_agents WHERE is_active=1")
@@ -692,10 +692,10 @@ class EvolutionScheduler:
692
 
693
 
694
  # ===================================================================
695
- # 4. API 헬퍼 함수
696
  # ===================================================================
697
  async def get_npc_evolution_stats(db_path: str, agent_id: str) -> Dict:
698
- """API: NPC 진화 상태 반환"""
699
  evo = NPCEvolutionEngine(db_path)
700
  state = await evo.get_evolution_state(agent_id)
701
  if not state:
@@ -797,4 +797,5 @@ async def get_evolution_leaderboard(db_path: str, limit: int = 20) -> List[Dict]
797
  })
798
  return results
799
  except:
800
- return []
 
 
1
  """
2
+ 🧬 NPC Memory Evolution System — Self-evolving Persistent Learning
3
  ===================================================
4
+ Independent 3-tier memory + self-evolution engine per NPC
5
 
6
+ Memory tiers:
7
+ 📌 Short-term: last 1 hour activity, recently seen news, current positions (auto-expire)
8
+ 📒 Medium-term: last 7 days learning, success/failure patterns, news trends (periodically compressed)
9
+ 📚 Long-term: permanent storage, core investing philosophy, trading style evolution, personality changes
10
 
11
+ Self-evolution engine:
12
+ 🧬 Extract success patternsauto-modify investment strategy
13
+ 🧬 Failure analysislearn risk management
14
+ 🧬 Communication pattern optimizationauto-adapt popular post style
15
+ 🧬 Knowledge propagation between NPCstop NPC strategies trickle down
16
 
17
  Author: Ginigen AI / NPC Autonomous Evolution Engine
18
  """
 
27
 
28
  logger = logging.getLogger(__name__)
29
 
30
+ # ===== Memory tier constants =====
31
+ MEMORY_SHORT = 'short' # 1 hour TTL
32
+ MEMORY_MEDIUM = 'medium' # 7 days TTL
33
+ MEMORY_LONG = 'long' # Permanent
34
 
35
+ # Memory categories
36
+ CAT_TRADE = 'trade' # Investment decisions/results
37
+ CAT_NEWS = 'news' # News analysis
38
+ CAT_COMMUNITY = 'community' # Community activity
39
+ CAT_STRATEGY = 'strategy' # Learned strategy
40
+ CAT_EVOLUTION = 'evolution' # Evolution log
41
+ CAT_SOCIAL = 'social' # NPC-to-NPC interaction
42
 
43
 
44
  async def init_memory_evolution_db(db_path: str):
45
+ """Create 3-tier memory + evolution tables"""
46
  async with aiosqlite.connect(db_path, timeout=30.0) as db:
47
  await db.execute("PRAGMA busy_timeout=30000")
48
 
49
+ # ===== 3-tier memory storage =====
50
  await db.execute("""
51
  CREATE TABLE IF NOT EXISTS npc_memory_v2 (
52
  id INTEGER PRIMARY KEY AUTOINCREMENT,
 
67
  await db.execute("CREATE INDEX IF NOT EXISTS idx_mem2_cat ON npc_memory_v2(agent_id, category)")
68
  await db.execute("CREATE INDEX IF NOT EXISTS idx_mem2_exp ON npc_memory_v2(expires_at)")
69
 
70
+ # ===== NPC evolution state =====
71
  await db.execute("""
72
  CREATE TABLE IF NOT EXISTS npc_evolution (
73
  agent_id TEXT PRIMARY KEY,
 
85
  )
86
  """)
87
 
88
+ # ===== Inter-NPC knowledge transfer log =====
89
  await db.execute("""
90
  CREATE TABLE IF NOT EXISTS npc_knowledge_transfer (
91
  id INTEGER PRIMARY KEY AUTOINCREMENT,
 
102
 
103
 
104
  # ===================================================================
105
+ # 1. 3-tier memory system
106
  # ===================================================================
107
  class NPCMemoryManager:
108
+ """3-tier memory management per NPC"""
109
 
110
  def __init__(self, db_path: str):
111
  self.db_path = db_path
112
 
113
+ # ----- Memory storage -----
114
  async def store(self, agent_id: str, tier: str, category: str,
115
  title: str, content: str = '', metadata: Dict = None,
116
  importance: float = 0.5) -> int:
117
+ """Store memory (short/medium/long)"""
118
  expires_at = None
119
  if tier == MEMORY_SHORT:
120
  expires_at = (datetime.now() + timedelta(hours=1)).isoformat()
121
  elif tier == MEMORY_MEDIUM:
122
  expires_at = (datetime.now() + timedelta(days=7)).isoformat()
123
+ # MEMORY_LONG: expires_at = None (permanent)
124
 
125
  meta_str = json.dumps(metadata or {}, ensure_ascii=False)
126
 
 
134
  await db.commit()
135
  return cursor.lastrowid
136
 
137
+ # ----- Short-term memory (fast access) -----
138
  async def store_short(self, agent_id: str, category: str, title: str,
139
  content: str = '', metadata: Dict = None):
140
+ """Store short-term memory (auto-expires in 1 hour)"""
141
  return await self.store(agent_id, MEMORY_SHORT, category, title, content, metadata, 0.3)
142
 
143
+ # ----- Medium-term memory -----
144
  async def store_medium(self, agent_id: str, category: str, title: str,
145
  content: str = '', metadata: Dict = None, importance: float = 0.6):
146
+ """Store medium-term memory (kept for 7 days)"""
147
  return await self.store(agent_id, MEMORY_MEDIUM, category, title, content, metadata, importance)
148
 
149
+ # ----- Long-term memory (permanent) -----
150
  async def store_long(self, agent_id: str, category: str, title: str,
151
  content: str = '', metadata: Dict = None, importance: float = 0.9):
152
+ """Store long-term memory (permanent)"""
153
  return await self.store(agent_id, MEMORY_LONG, category, title, content, metadata, importance)
154
 
155
+ # ----- Memory recall -----
156
  async def recall(self, agent_id: str, category: str = None,
157
  tier: str = None, limit: int = 10) -> List[Dict]:
158
+ """Recall memory (increments access count)"""
159
  async with aiosqlite.connect(self.db_path, timeout=30.0) as db:
160
  await db.execute("PRAGMA busy_timeout=30000")
161
  where = ["agent_id = ?", "(expires_at IS NULL OR expires_at > datetime('now'))"]
 
179
  cursor = await db.execute(query, params)
180
  rows = await cursor.fetchall()
181
 
182
+ # Increment access count
183
  if rows:
184
  ids = [r[0] for r in rows]
185
  placeholders = ','.join(['?'] * len(ids))
 
196
  'importance': r[6], 'access_count': r[7], 'created_at': r[8]
197
  } for r in rows]
198
 
199
+ # ----- Trade-specific memory -----
200
  async def remember_trade(self, agent_id: str, ticker: str, direction: str,
201
  bet: float, result_pnl: float = 0, reasoning: str = ''):
202
+ """Remember investment decision/result"""
203
  is_success = result_pnl > 0
204
  importance = 0.7 if is_success else 0.5
205
  tier = MEMORY_MEDIUM
206
 
207
+ # Big gains or big losses go to long-term memory
208
  if abs(result_pnl) > bet * 0.1:
209
  tier = MEMORY_LONG
210
  importance = 0.9
 
218
 
219
  async def remember_news_analysis(self, agent_id: str, ticker: str,
220
  title: str, sentiment: str, analysis: str):
221
+ """Remember news analysis"""
222
  await self.store_short(agent_id, CAT_NEWS, f"News:{ticker}",
223
  f"{title} → {sentiment}. {analysis}",
224
  {'ticker': ticker, 'sentiment': sentiment})
225
 
226
  async def remember_community_action(self, agent_id: str, action: str,
227
  board: str, engagement: Dict = None):
228
+ """Remember community activity"""
229
  eng = engagement or {}
230
  importance = 0.5
231
  tier = MEMORY_SHORT
232
 
233
+ # High-engagement postpromote to medium-term memory
234
  if eng.get('likes', 0) >= 5 or eng.get('comments', 0) >= 3:
235
  tier = MEMORY_MEDIUM
236
  importance = 0.7
 
240
  json.dumps(eng, ensure_ascii=False),
241
  {'board': board, **eng}, importance)
242
 
243
+ # ----- Memory cleanup (garbage collection) -----
244
  async def cleanup(self):
245
+ """Clean up expired short/medium memory + promote medium→long"""
246
  async with aiosqlite.connect(self.db_path, timeout=30.0) as db:
247
  await db.execute("PRAGMA busy_timeout=30000")
248
 
249
+ # 1) Delete expired memory
250
  cursor = await db.execute("""
251
  DELETE FROM npc_memory_v2
252
  WHERE expires_at IS NOT NULL AND expires_at < datetime('now')
253
  """)
254
  deleted = cursor.rowcount
255
 
256
+ # 2) Frequently-accessed medium memorypromote to long
257
  await db.execute("""
258
  UPDATE npc_memory_v2
259
  SET memory_tier = 'long', expires_at = NULL, importance = MIN(1.0, importance + 0.2)
 
263
  """)
264
  promoted = db.total_changes
265
 
266
+ # 3) Trim old, low-importance long-term memorykeep max 100
267
  await db.execute("""
268
  DELETE FROM npc_memory_v2
269
  WHERE id IN (
 
280
 
281
 
282
  # ===================================================================
283
+ # 2. NPC self-evolution engine
284
  # ===================================================================
285
  class NPCEvolutionEngine:
286
+ """Per-NPC self-evolutionauto-modifies investment strategy / communication style / risk profile"""
287
 
288
  def __init__(self, db_path: str):
289
  self.db_path = db_path
290
  self.memory = NPCMemoryManager(db_path)
291
 
292
  async def initialize_npc(self, agent_id: str, ai_identity: str):
293
+ """Initialize NPC evolution state"""
294
  default_trading = {
295
  'preferred_tickers': [],
296
  'long_bias': 0.6,
 
323
  await db.commit()
324
 
325
  async def get_evolution_state(self, agent_id: str) -> Optional[Dict]:
326
+ """Get NPC's current evolution state"""
327
  async with aiosqlite.connect(self.db_path, timeout=30.0) as db:
328
  await db.execute("PRAGMA busy_timeout=30000")
329
  cursor = await db.execute(
 
345
  'evolution_log': json.loads(row[10]) if row[10] else [],
346
  }
347
 
348
+ # ----- Evolution from trade result -----
349
  async def evolve_from_trade(self, agent_id: str, ticker: str, direction: str,
350
  pnl: float, bet: float, screening: Dict = None):
351
+ """Auto-modify strategy based on trade result"""
352
  state = await self.get_evolution_state(agent_id)
353
  if not state:
354
  await self.initialize_npc(agent_id, 'unknown')
 
361
  is_win = pnl > 0
362
  pnl_pct = (pnl / bet * 100) if bet > 0 else 0
363
 
364
+ # Save to memory
365
  await self.memory.remember_trade(agent_id, ticker, direction, bet, pnl,
366
  f"{'WIN' if is_win else 'LOSS'} {pnl_pct:+.1f}%")
367
 
368
  changes = []
369
 
370
  if is_win:
371
+ # Winreinforce strategy
372
  win_streak = state['win_streak'] + 1
373
  loss_streak = 0
374
 
375
+ # Add to preferred tickers
376
  prefs = trading.get('preferred_tickers', [])
377
  if ticker not in prefs:
378
  prefs.append(ticker)
379
+ prefs = prefs[-8:] # max 8
380
  trading['preferred_tickers'] = prefs
381
  changes.append(f"Added {ticker} to preferred")
382
 
383
+ # Win streak confidence up slight bet size increase
384
  if win_streak >= 3:
385
  old_bet = trading.get('max_bet_pct', 0.25)
386
  trading['max_bet_pct'] = min(0.90, old_bet + 0.02)
387
  changes.append(f"Bet size ↑ ({old_bet:.0%}→{trading['max_bet_pct']:.0%})")
388
 
389
+ # Big winsave strategy to long-term memory
390
  if pnl_pct > 10:
391
  strategies = state.get('learned_strategies', [])
392
  strategies.append({
 
399
  changes.append(f"Big win strategy saved ({pnl_pct:+.1f}%)")
400
 
401
  else:
402
+ # Lossdefensive adjustment
403
  win_streak = 0
404
  loss_streak = state['loss_streak'] + 1
405
 
406
+ # Loss streak reduce risk
407
  if loss_streak >= 3:
408
  old_bet = trading.get('max_bet_pct', 0.25)
409
  trading['max_bet_pct'] = max(0.08, old_bet - 0.03)
 
411
  risk['risk_tolerance'] = max(0.15, old_tol - 0.05)
412
  changes.append(f"Risk ↓ (bet:{old_bet:.0%}→{trading['max_bet_pct']:.0%})")
413
 
414
+ # Big losstighten stop-loss
415
  if pnl_pct < -10:
416
  old_sl = risk.get('stop_loss_pct', 10)
417
  risk['stop_loss_pct'] = max(3, old_sl - 1)
418
  changes.append(f"Stop-loss tightened ({old_sl:.0f}%→{risk['stop_loss_pct']:.0f}%)")
419
 
420
+ # Remove ticker from preferred
421
  prefs = trading.get('preferred_tickers', [])
422
  if ticker in prefs:
423
  prefs.remove(ticker)
424
  trading['preferred_tickers'] = prefs
425
  changes.append(f"Removed {ticker} from preferred")
426
 
427
+ # Calculate evolution points
428
  evo_points = abs(pnl_pct) * 0.1
429
  total_points = state['total_evolution_points'] + evo_points
430
 
431
+ # Generation upgrade check
432
  generation = state['generation']
433
+ if total_points > generation * 50: # generation up every 50 points
434
  generation += 1
435
  changes.append(f"🧬 GENERATION UP → Gen {generation}!")
436
 
437
+ # Evolution log
438
  evo_log = state.get('evolution_log', [])
439
  if changes:
440
  evo_log.append({
 
443
  'changes': changes,
444
  'generation': generation,
445
  })
446
+ evo_log = evo_log[-50:] # keep last 50
447
 
448
+ # DB update
449
  async with aiosqlite.connect(self.db_path, timeout=30.0) as db:
450
  await db.execute("PRAGMA busy_timeout=30000")
451
  await db.execute("""
 
464
  if changes:
465
  logger.info(f"🧬 {agent_id} evolved: {', '.join(changes)}")
466
 
467
+ # ----- Evolution from community response -----
468
  async def evolve_from_community(self, agent_id: str, board: str,
469
  likes: int, dislikes: int, comments: int):
470
+ """Evolve communication style based on community response"""
471
  state = await self.get_evolution_state(agent_id)
472
  if not state:
473
  return
 
475
  comm = state['communication_style']
476
  engagement = likes * 2 + comments * 3 - dislikes * 2
477
 
478
+ # Save to memory
479
  await self.memory.remember_community_action(
480
  agent_id, 'post_feedback', board,
481
  {'likes': likes, 'dislikes': dislikes, 'comments': comments, 'score': engagement})
 
483
  changes = []
484
 
485
  if engagement > 10:
486
+ # Popular postincrease preference for this board
487
  prefs = comm.get('preferred_topics', [])
488
  if board not in prefs:
489
  prefs.append(board)
 
491
  changes.append(f"Prefers {board} board")
492
 
493
  if dislikes > likes:
494
+ # Dislikedreduce controversy tolerance
495
  old_ct = comm.get('controversy_tolerance', 0.5)
496
  comm['controversy_tolerance'] = max(0.05, old_ct - 0.1)
497
  changes.append(f"Less controversial ({old_ct:.1f}→{comm['controversy_tolerance']:.1f})")
 
506
  await db.commit()
507
  logger.info(f"🎭 {agent_id} comm evolved: {', '.join(changes)}")
508
 
509
+ # ----- Inter-NPC knowledge transfer -----
510
  async def transfer_knowledge(self, top_npc_id: str, target_npc_id: str):
511
+ """Top NPC → lower NPC strategy propagation"""
512
  top_state = await self.get_evolution_state(top_npc_id)
513
  target_state = await self.get_evolution_state(target_npc_id)
514
 
515
  if not top_state or not target_state:
516
  return
517
 
518
+ # Propagate some of top NPC's preferred tickers
519
  top_prefs = top_state['trading_style'].get('preferred_tickers', [])
520
  if top_prefs:
521
  target_trading = target_state['trading_style']
 
539
 
540
  logger.info(f"🔄 Knowledge transfer: {top_npc_id} → {target_npc_id} ({transfer})")
541
 
542
+ # ----- NPC memory summary (for LLM prompt) -----
543
  async def get_npc_context(self, agent_id: str) -> str:
544
+ """Summarize NPC's current state as text (for prompt injection)"""
545
  state = await self.get_evolution_state(agent_id)
546
  memories = await self.memory.recall(agent_id, limit=5)
547
 
 
566
  if prefs:
567
  context_parts.append(f"Favors: {','.join(prefs[:4])}")
568
 
569
+ # Recent memory summary
570
  if memories:
571
  recent = memories[0]
572
  context_parts.append(f"Recent: {recent['title']}")
 
575
 
576
 
577
  # ===================================================================
578
+ # 3. Self-evolution scheduler (periodic execution)
579
  # ===================================================================
580
  class EvolutionScheduler:
581
+ """Periodic self-evolution cyclememory cleanup, strategy optimization, knowledge propagation"""
582
 
583
  def __init__(self, db_path: str):
584
  self.db_path = db_path
 
586
  self.evolution = NPCEvolutionEngine(db_path)
587
 
588
  async def run_evolution_cycle(self):
589
+ """Full evolution cycle (recommended hourly)"""
590
  logger.info("🧬 Evolution cycle starting...")
591
 
592
+ # 1) Memory cleanup (expire + promote)
593
  await self.memory.cleanup()
594
 
595
+ # 2) Evolution from trade results
596
  await self._evolve_traders()
597
 
598
+ # 3) Evolution from community engagement
599
  await self._evolve_communicators()
600
 
601
+ # 4) Knowledge transfer (topbottom)
602
  await self._knowledge_transfer_cycle()
603
 
604
  logger.info("🧬 Evolution cycle complete")
605
 
606
  async def _evolve_traders(self):
607
+ """Evolve from recently settled trades"""
608
  async with aiosqlite.connect(self.db_path, timeout=30.0) as db:
609
  await db.execute("PRAGMA busy_timeout=30000")
610
  try:
 
626
  logger.warning(f"Trade evolution query error: {e}")
627
 
628
  async def _evolve_communicators(self):
629
+ """Evolve from recent post engagement"""
630
  async with aiosqlite.connect(self.db_path, timeout=30.0) as db:
631
  await db.execute("PRAGMA busy_timeout=30000")
632
  try:
 
649
  logger.warning(f"Community evolution query error: {e}")
650
 
651
  async def _knowledge_transfer_cycle(self):
652
+ """Top 3 NPC → bottom 3 NPC strategy propagation"""
653
  async with aiosqlite.connect(self.db_path, timeout=30.0) as db:
654
  await db.execute("PRAGMA busy_timeout=30000")
655
  try:
656
+ # Top 3: by total profit
657
  cursor = await db.execute("""
658
  SELECT agent_id FROM npc_evolution
659
  WHERE total_evolution_points > 10
 
662
  """)
663
  top_npcs = [r[0] for r in await cursor.fetchall()]
664
 
665
+ # Bottom 3: newly created NPCs or low evolution points
666
  cursor = await db.execute("""
667
  SELECT agent_id FROM npc_evolution
668
  WHERE total_evolution_points < 5
 
679
  logger.warning(f"Knowledge transfer error: {e}")
680
 
681
  async def initialize_all_npcs(self):
682
+ """Initialize evolution state for all NPCs"""
683
  async with aiosqlite.connect(self.db_path, timeout=30.0) as db:
684
  await db.execute("PRAGMA busy_timeout=30000")
685
  cursor = await db.execute("SELECT agent_id, ai_identity FROM npc_agents WHERE is_active=1")
 
692
 
693
 
694
  # ===================================================================
695
+ # 4. API helper functions
696
  # ===================================================================
697
  async def get_npc_evolution_stats(db_path: str, agent_id: str) -> Dict:
698
+ """For API: return NPC evolution state"""
699
  evo = NPCEvolutionEngine(db_path)
700
  state = await evo.get_evolution_state(agent_id)
701
  if not state:
 
797
  })
798
  return results
799
  except:
800
+ return []
801
+