shadowbrain / shadow_brain_core /compaction.py
taemin1980's picture
πŸ”± Imperial Deployment: Shadow Brain Core ignition
d50a68d verified
Raw
History Blame Contribute Delete
7.04 kB
"""
πŸ”± Imperial Compaction Engine (μ§€λŠ₯ν˜• μ»¨ν…μŠ€νŠΈ μ••μΆ• μ—”μ§„)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Claw Code의 'Compaction' κΈ°μˆ μ„ μ‰λ„μš° 브레인 코어에 μ΅œμ ν™”ν•˜μ—¬ μ΄μ‹ν•œ λͺ¨λ“ˆμž…λ‹ˆλ‹€.
λŒ€ν™”κ°€ κΈΈμ–΄μ§ˆ λ•Œ 핡심 μ»¨ν…μŠ€νŠΈ(파일, TODO, νƒ€μž„λΌμΈ)λ₯Ό λ³΄μ‘΄ν•˜λ©° 토큰을 μ ˆμ•½ν•©λ‹ˆλ‹€.
"""
import os
import json
import time
import re
from typing import List, Dict, Any, Optional
class CompactionEngine:
"""λŒ€ν™” 내역을 λΆ„μ„ν•˜κ³  μ§€λŠ₯적으둜 μ••μΆ•ν•˜λŠ” 핡심 μ—”μ§„."""
PREAMBLE = "이 μ„Έμ…˜μ€ 이전 λŒ€ν™”μ—μ„œ μ»¨ν…μŠ€νŠΈκ°€ λΆ€μ‘±ν•˜μ—¬ μš”μ•½λ³ΈμœΌλ‘œλΆ€ν„° μ΄μ–΄μ§€λŠ” λ‚΄μš©μž…λ‹ˆλ‹€.\n\n"
def __init__(self, project_root: str):
self.project_root = project_root
self.settings_file = os.path.join(project_root, "docker_data", "shared_workspace", "JarvisRun", "JARVIS_SHADOW_SETTINGS.json")
def _get_settings(self) -> Dict[str, Any]:
"""μ„€μ • νŒŒμΌμ—μ„œ ν•«-λ‘œλ“œ(Hot-Reload)둜 섀정을 μ½μ–΄μ˜΅λ‹ˆλ‹€."""
defaults = {
"compaction_enabled": True,
"compaction_threshold": 50, # λ©”μ‹œμ§€ 개수 κΈ°μ€€
"preserve_recent": 5 # μ••μΆ• μ‹œ 보쑴할 μ΅œμ‹  λ©”μ‹œμ§€ 수
}
if os.path.exists(self.settings_file):
try:
with open(self.settings_file, 'r', encoding='utf-8') as f:
data = json.load(f)
# merge with defaults
for k, v in defaults.items():
if k not in data: data[k] = v
return data
except: pass
return defaults
def should_compact(self, messages: List[Dict[str, Any]]) -> bool:
"""압좕이 ν•„μš”ν•œ μ‹œμ μΈμ§€ νŒλ‹¨ν•©λ‹ˆλ‹€."""
settings = self._get_settings()
if not settings.get("compaction_enabled", True):
return False
threshold = int(settings.get("compaction_threshold", 50))
return len(messages) >= threshold
def extract_metadata(self, messages: List[Dict[str, Any]]) -> Dict[str, Any]:
"""λŒ€ν™” λ‚΄μ—­μ—μ„œ 핡심 메타데이터(파일, TODO, νƒ€μž„λΌμΈ, 감정)λ₯Ό μΆ”μΆœν•©λ‹ˆλ‹€."""
files = set()
todos = []
timeline = []
emotions = {} # sender별 주둜 ν‘œν˜„λœ 감정
# 파일 ν™•μž₯자 μ •κ·œμ‹
file_pattern = re.compile(r'[\w/._-]+\.(?:py|rs|dart|js|ts|md|html|css|json|yaml|yml|sh|ps1|bat)')
for msg in messages:
content = msg.get('text', '') or ''
role = msg.get('relayGuardian') or msg.get('sender_name') or msg.get('sender_id') or 'unknown'
emotion = msg.get('emotion')
# 1. 파일 경둜 μΆ”μΆœ
found_files = file_pattern.findall(content)
for f in found_files:
if len(f) > 3: files.add(f)
# 2. TODO/Next Step μΆ”λ‘ 
lower_content = content.lower()
if any(kw in lower_content for kw in ['todo', 'λ‹€μŒ', 'next', 'ν•  일', 'pending', 'remain']):
lines = [line.strip() for line in content.split('\n') if line.strip()]
for line in lines:
if any(kw in line.lower() for kw in ['todo', 'λ‹€μŒ', 'next', 'ν•  일']):
todos.append(line[:150] + "..." if len(line) > 150 else line)
# 3. 감정 좔적
if emotion:
if role not in emotions: emotions[role] = []
emotions[role].append(emotion)
# 4. νƒ€μž„λΌμΈ (핡심 이벀트 흐름 μš”μ•½)
snippet = content.replace('\n', ' ').strip()
if len(snippet) > 80: snippet = snippet[:77] + "..."
emo_str = f" ({emotion})" if emotion else ""
timeline.append(f"{role}{emo_str}: {snippet}")
# κ°€μž₯ λΉˆλ²ˆν•œ 감정 μš”μ•½
summary_emotions = {}
for role, emo_list in emotions.items():
if emo_list:
from collections import Counter
most_common = Counter(emo_list).most_common(1)[0][0]
summary_emotions[role] = most_common
return {
"files": sorted(list(files))[:15],
"todos": todos[-5:],
"timeline": timeline[-20:],
"emotions": summary_emotions,
"count": len(messages)
}
def generate_summary_message(self, metadata: Dict[str, Any]) -> str:
"""μΆ”μΆœλœ 메타데이터λ₯Ό μ‚¬μš©ν•˜μ—¬ λ§ˆν¬λ‹€μš΄ μš”μ•½λ³Έμ„ μƒμ„±ν•©λ‹ˆλ‹€."""
summary = ["<summary>", "πŸ”± **System Context Summary (이전 λŒ€ν™” μš”μ•½)**"]
summary.append(f"\n- **μ••μΆ• λ²”μœ„**: 이전 {metadata['count']}개의 λ©”μ‹œμ§€ 톡합")
if metadata['emotions']:
emo_lines = [f"{role}: {emo}" for role, emo in metadata['emotions'].items()]
summary.append(f"- **μ£Όμš” 감정 μƒνƒœ**: {', '.join(emo_lines)}")
if metadata['files']:
summary.append(f"- **참쑰된 μ£Όμš” 파일**: {', '.join([f'`{f}`' for f in metadata['files']])}")
if metadata['todos']:
summary.append("- **남은 과제 및 TODO**:")
for todo in metadata['todos']:
summary.append(f" - {todo}")
summary.append("- **μ£Όμš” νƒ€μž„λΌμΈ**:")
for event in metadata['timeline']:
summary.append(f" - {event}")
summary.append("\n</summary>")
return self.PREAMBLE + "\n".join(summary)
def compact(self, messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""λ©”μ‹œμ§€ 리슀트λ₯Ό μ†Œλ²„λ¦° ν•˜λ„€μŠ€ λ°©μ‹μœΌλ‘œ μ••μΆ•ν•©λ‹ˆλ‹€."""
if not self.should_compact(messages):
return messages
settings = self._get_settings()
preserve_count = int(settings.get("preserve_recent", 5))
# μ••μΆ• λŒ€μƒκ³Ό 보쑴 λŒ€μƒ 뢄리
split_idx = len(messages) - preserve_count
to_compact = messages[:split_idx]
preserved = messages[split_idx:]
# μš”μ•½ 생성
metadata = self.extract_metadata(to_compact)
summary_text = self.generate_summary_message(metadata)
# μ‹œμŠ€ν…œ μš”μ•½ λ©”μ‹œμ§€ 생성
summary_msg = {
"text": summary_text,
"type": 3, # πŸ”± Imperial Type 3: System Context Summary
"relayGuardian": "System",
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ"),
"sender_id": "97157d36-0b88-473f-8131-fab3e46aa560" # Shadow Brain ID
}
return [summary_msg] + preserved
# Global Singleton for Engine
import os
core_root = os.path.dirname(os.path.abspath(__file__))
compaction_engine = CompactionEngine(core_root)