live_escalator.py
1 #!/usr/bin/env python3 2 """ 3 Live Escalator - Watch conversation and auto-escalate high-resonance insights 4 5 This is the pipeline that was missing: 6 [Statement] → semantic_resonance.py → threshold check → RESONANCE-ALERTS/ 7 8 Building this to understand what "automatic escalation" means. 9 10 Usage: 11 # Watch a transcript file 12 python scripts/live_escalator.py --watch /path/to/transcript.jsonl 13 14 # Process a single statement 15 echo "building is cheap" | python scripts/live_escalator.py 16 17 # Run as daemon 18 python scripts/live_escalator.py --daemon --transcript /path/to/session.jsonl 19 """ 20 21 import os 22 import re 23 import sys 24 import json 25 import time 26 from pathlib import Path 27 from datetime import datetime 28 from typing import Optional, List, Dict 29 import argparse 30 31 # Import semantic resonance if available 32 sys.path.insert(0, str(Path(__file__).parent)) 33 34 try: 35 from semantic_resonance import SemanticResonanceEngine 36 HAS_SEMANTIC = True 37 except ImportError: 38 HAS_SEMANTIC = False 39 print("Warning: semantic_resonance not available, using term-based fallback") 40 41 42 # Fallback term-based resonance 43 AXIOM_TERMS = { 44 'A0': {'boundary', 'boundaries', 'markov', 'blanket', 'structure', 'inside', 'outside', 'interface'}, 45 'A1': {'integration', 'connect', 'binding', 'relation', 'isolation', 'together'}, 46 'A2': {'life', 'death', 'alive', 'primitive', 'calcified', 'motion', 'fixation', 'real'}, 47 'A3': {'pole', 'poles', 'navigation', 'tension', 'dynamic', 'balance', 'between'}, 48 'A4': {'ruin', 'survival', 'cheap', 'rebuild', 'rewrite', 'catastrophe', 'ergodic', 'terminal'}, 49 } 50 51 52 def extract_user_statements(transcript_path: Path, after_line: int = 0) -> List[Dict]: 53 """Extract user statements from Claude transcript.""" 54 statements = [] 55 56 try: 57 with open(transcript_path, 'r') as f: 58 for i, line in enumerate(f): 59 if i < after_line: 60 continue 61 try: 62 entry = json.loads(line) 63 if entry.get('type') == 'user': 64 content = entry.get('message', {}).get('content', '') 65 if isinstance(content, str) and len(content) > 20: 66 statements.append({ 67 'line': i, 68 'text': content[:500], # Truncate 69 'timestamp': entry.get('timestamp', ''), 70 }) 71 except json.JSONDecodeError: 72 continue 73 except Exception as e: 74 print(f"Error reading transcript: {e}", file=sys.stderr) 75 76 return statements 77 78 79 def calculate_resonance_fallback(text: str) -> Dict[str, float]: 80 """Fallback term-based resonance.""" 81 text_lower = text.lower() 82 words = set(re.findall(r'\w+', text_lower)) 83 84 scores = {} 85 for axiom, terms in AXIOM_TERMS.items(): 86 overlap = len(words & terms) 87 scores[axiom] = min(1.0, overlap * 0.2) 88 89 return scores 90 91 92 def calculate_resonance(text: str) -> Dict[str, float]: 93 """Calculate resonance using semantic engine or fallback.""" 94 if HAS_SEMANTIC: 95 try: 96 engine = SemanticResonanceEngine() 97 result = engine.analyze(text) 98 return {r['axiom']: r['score'] for r in result.get('resonances', [])} 99 except Exception: 100 pass 101 102 return calculate_resonance_fallback(text) 103 104 105 def create_alert( 106 text: str, 107 resonances: Dict[str, float], 108 alerts_dir: Path, 109 threshold: float = 0.4 110 ) -> Optional[Path]: 111 """Create alert if resonance exceeds threshold.""" 112 max_axiom = max(resonances, key=resonances.get) 113 max_score = resonances[max_axiom] 114 115 if max_score < threshold: 116 return None 117 118 timestamp = datetime.now().strftime('%Y%m%d-%H%M%S') 119 filename = f"{timestamp}-auto-escalation-{max_axiom.lower()}.md" 120 filepath = alerts_dir / filename 121 122 lines = [ 123 f"# Auto-Escalation - {max_axiom}", 124 "", 125 f"**Type:** HIGH_RESONANCE_STATEMENT", 126 f"**Detected:** {datetime.now().isoformat()}", 127 f"**Axiom:** {max_axiom}", 128 f"**Score:** {max_score:.0%}", 129 "", 130 "---", 131 "", 132 "## Statement", 133 "", 134 f"> {text[:300]}{'...' if len(text) > 300 else ''}", 135 "", 136 "## All Resonances", 137 "", 138 ] 139 140 for axiom in ['A0', 'A1', 'A2', 'A3', 'A4']: 141 score = resonances.get(axiom, 0) 142 bar = '█' * int(score * 10) 143 lines.append(f"- {axiom}: [{score:>5.0%}] {bar}") 144 145 lines.extend([ 146 "", 147 "## Action Required", 148 "", 149 f"This statement has high resonance ({max_score:.0%}) with {max_axiom}.", 150 "Consider:", 151 "- Adding to canonical principles", 152 "- Integrating into relevant documentation", 153 "- Extracting as derived principle", 154 "", 155 "---", 156 "", 157 f"*Auto-generated by Live Escalator | {datetime.now().strftime('%Y-%m-%d %H:%M')}*", 158 ]) 159 160 filepath.write_text('\n'.join(lines), encoding='utf-8') 161 return filepath 162 163 164 def process_statement( 165 text: str, 166 alerts_dir: Path, 167 threshold: float = 0.4, 168 quiet: bool = False 169 ) -> Optional[Path]: 170 """Process a single statement.""" 171 resonances = calculate_resonance(text) 172 max_axiom = max(resonances, key=resonances.get) 173 max_score = resonances[max_axiom] 174 175 if not quiet: 176 print(f"[{max_score:>5.0%}] {max_axiom}: {text[:60]}...") 177 178 if max_score >= threshold: 179 alert = create_alert(text, resonances, alerts_dir, threshold) 180 if alert and not quiet: 181 print(f" → Alert created: {alert.name}") 182 return alert 183 184 return None 185 186 187 def watch_transcript( 188 transcript_path: Path, 189 alerts_dir: Path, 190 threshold: float = 0.4, 191 interval: float = 5.0 192 ): 193 """Watch transcript and escalate new statements.""" 194 print(f"Watching: {transcript_path}") 195 print(f"Alerts: {alerts_dir}") 196 print(f"Threshold: {threshold:.0%}") 197 print() 198 199 last_line = 0 200 alerts_created = 0 201 202 while True: 203 statements = extract_user_statements(transcript_path, last_line) 204 205 for stmt in statements: 206 alert = process_statement( 207 stmt['text'], 208 alerts_dir, 209 threshold, 210 quiet=False 211 ) 212 if alert: 213 alerts_created += 1 214 last_line = max(last_line, stmt['line'] + 1) 215 216 time.sleep(interval) 217 218 219 def main(): 220 parser = argparse.ArgumentParser( 221 description='Live Escalator - Auto-escalate high-resonance insights' 222 ) 223 parser.add_argument( 224 '--text', '-t', 225 type=str, 226 help='Single statement to process' 227 ) 228 parser.add_argument( 229 '--watch', '-w', 230 type=Path, 231 help='Transcript file to watch' 232 ) 233 parser.add_argument( 234 '--threshold', 235 type=float, 236 default=0.4, 237 help='Resonance threshold for escalation (default: 0.4)' 238 ) 239 parser.add_argument( 240 '--alerts-dir', 241 type=Path, 242 default=Path(__file__).parent.parent / 'sessions' / 'RESONANCE-ALERTS', 243 help='Directory for alerts' 244 ) 245 parser.add_argument( 246 '--interval', 247 type=float, 248 default=5.0, 249 help='Watch interval in seconds' 250 ) 251 252 args = parser.parse_args() 253 254 args.alerts_dir.mkdir(parents=True, exist_ok=True) 255 256 if args.text: 257 process_statement(args.text, args.alerts_dir, args.threshold) 258 elif args.watch: 259 watch_transcript(args.watch, args.alerts_dir, args.threshold, args.interval) 260 elif not sys.stdin.isatty(): 261 text = sys.stdin.read().strip() 262 if text: 263 process_statement(text, args.alerts_dir, args.threshold) 264 else: 265 parser.print_help() 266 return 1 267 268 return 0 269 270 271 if __name__ == '__main__': 272 sys.exit(main())