/ core / attention / unified.py
unified.py
  1  """
  2  Unified Attention System
  3  
  4  Wires together all attention subsystems:
  5  - AttentionTracker: tracks where attention lands
  6  - CrossSessionTracker: tracks attention across concurrent sessions
  7  - AhaDetector: detects when attention crystallizes into insight
  8  - SessionMembrane: controls context flow between sessions
  9  
 10  "Attention is all you need" - this module is the integration point.
 11  """
 12  
 13  from dataclasses import dataclass, field
 14  from datetime import datetime
 15  from typing import Optional, List, Dict, Tuple
 16  from pathlib import Path
 17  
 18  from .tracker import AttentionTracker, AttentionEvent, create_attention_system
 19  from .cross_session import CrossSessionTracker, SessionStreamWatcher, create_cross_session_system
 20  from .aha_detection import AhaDetector, AhaManager, AhaSurfacingConfig, AhaSurfaceTarget, create_aha_system
 21  from .membrane import SessionMembrane, PermeabilityLevel, create_membrane_system
 22  from .compaction import AttentionCompactor, DailyNoteCompactor, create_compaction_system
 23  
 24  
 25  @dataclass
 26  class UnifiedAttentionConfig:
 27      """Configuration for the unified attention system."""
 28      sessions_dir: str
 29      daily_notes_dir: str
 30      permeability: PermeabilityLevel = PermeabilityLevel.ATTRACTORS
 31      enable_aha_detection: bool = True
 32      enable_compaction: bool = True
 33      aha_min_confidence: float = 0.6
 34      aha_audio_enabled: bool = False
 35  
 36  
 37  class UnifiedAttentionSystem:
 38      """
 39      The unified attention system that wires everything together.
 40  
 41      This is the single entry point for the attention architecture.
 42      All subsystems are connected and events flow between them.
 43  
 44      Usage:
 45          system = UnifiedAttentionSystem(UnifiedAttentionConfig(
 46              sessions_dir="/path/to/sessions/",
 47              daily_notes_dir="/path/to/daily/"
 48          ))
 49  
 50          # Process incoming attention events
 51          system.record_attention(event)
 52  
 53          # Get unified state
 54          state = system.get_state()
 55  
 56          # Check for aha moments
 57          ahas = system.get_recent_ahas()
 58  
 59          # Run compaction
 60          system.compact_daily_note("2026-01-13.md")
 61      """
 62  
 63      def __init__(self, config: UnifiedAttentionConfig):
 64          self.config = config
 65  
 66          # Create subsystems
 67          self.attention_tracker, self.daily_integration = create_attention_system()
 68  
 69          self.cross_session_tracker, self.session_watcher = create_cross_session_system(
 70              sessions_dir=config.sessions_dir
 71          )
 72  
 73          if config.enable_aha_detection:
 74              self.aha_detector, self.aha_manager = create_aha_system(
 75                  daily_note_path=config.daily_notes_dir,
 76                  audio_enabled=config.aha_audio_enabled
 77              )
 78          else:
 79              self.aha_detector = None
 80              self.aha_manager = None
 81  
 82          self.membrane = create_membrane_system(
 83              sessions_dir=config.sessions_dir,
 84              permeability=config.permeability
 85          )
 86  
 87          if config.enable_compaction:
 88              self.compactor, self.daily_compactor = create_compaction_system()
 89          else:
 90              self.compactor = None
 91              self.daily_compactor = None
 92  
 93          # Wire the subsystems together
 94          self._wire_subsystems()
 95  
 96      def _wire_subsystems(self) -> None:
 97          """Connect the subsystems so events flow between them."""
 98  
 99          # Cross-session convergence → Aha detection
100          if self.aha_detector:
101              def on_convergence(topic: str, session_ids: List[str], intensity: float):
102                  """When sessions converge on a topic, check for aha."""
103                  self.aha_detector.detect_from_cross_session(
104                      topic=topic,
105                      session_ids=session_ids,
106                      combined_intensity=intensity
107                  )
108  
109              self.cross_session_tracker.on_convergence(on_convergence)
110  
111          # Attention events → Cross-session tracking
112          # (handled via record_attention method below)
113  
114      def record_attention(
115          self,
116          event: AttentionEvent,
117          session_id: str = None
118      ) -> None:
119          """
120          Record an attention event.
121  
122          This feeds the event to all relevant subsystems:
123          - Attention tracker (trajectory computation)
124          - Cross-session tracker (if session_id provided)
125          - Resonance check for aha detection
126  
127          Args:
128              event: The attention event to record
129              session_id: Optional session ID for cross-session tracking
130          """
131          # Record in attention tracker
132          self.attention_tracker.record(event)
133  
134          # If we have a session ID, also track cross-session
135          if session_id:
136              # Extract topics from the event
137              topics = [event.target_type]  # Minimal topic extraction
138              if hasattr(event, 'metadata') and event.metadata:
139                  topics.extend(event.metadata.get('topics', []))
140  
141              self.cross_session_tracker.record_activity(
142                  session_id=session_id,
143                  atom_uuid=event.target_id,
144                  topics=topics
145              )
146  
147      def check_for_resonance_aha(
148          self,
149          concept: str,
150          resonance_before: float,
151          resonance_after: float,
152          related_concepts: List[str]
153      ) -> None:
154          """
155          Check if a resonance change indicates an aha moment.
156  
157          Call this when resonance scores change significantly.
158          """
159          if self.aha_detector:
160              self.aha_detector.detect_from_resonance_spike(
161                  concept=concept,
162                  resonance_before=resonance_before,
163                  resonance_after=resonance_after,
164                  related_concepts=related_concepts
165              )
166  
167      def process_session_updates(self) -> Dict:
168          """
169          Process updates from all session streams.
170  
171          This is the main loop method - call periodically to:
172          1. Scan for new sessions
173          2. Read new content from session files
174          3. Detect cross-pollination
175          4. Trigger aha detection
176  
177          Returns:
178              Dict with processing stats
179          """
180          # Process session stream updates
181          cross_events = self.session_watcher.process_updates()
182  
183          # Get membrane signals for current session
184          focus_session = self.cross_session_tracker.get_state().focus_session
185          membrane_signals = []
186          if focus_session:
187              membrane_signals = self.membrane.get_signals_for_session(focus_session)
188  
189          return {
190              'cross_events': len(cross_events),
191              'membrane_signals': len(membrane_signals),
192              'active_sessions': len(self.cross_session_tracker.get_state().sessions),
193              'cross_session_attractors': self.cross_session_tracker.get_state().cross_session_attractors
194          }
195  
196      def get_state(self) -> Dict:
197          """
198          Get the unified attention state across all subsystems.
199  
200          Returns a combined view of:
201          - Current attention focus and trajectory
202          - Cross-session state
203          - Recent aha candidates
204          - Membrane permeability status
205          """
206          attention_state = self.attention_tracker.get_state()
207          cross_state = self.cross_session_tracker.get_state()
208  
209          state = {
210              'attention': {
211                  'current_focus': attention_state.current_focus,
212                  'trajectory_confidence': attention_state.trajectory.confidence if attention_state.trajectory else 0,
213                  'active_wells': [w.topic for w in attention_state.gravity_wells[:5]],
214                  'unresolved_count': len(attention_state.unresolved)
215              },
216              'cross_session': {
217                  'active_sessions': len(cross_state.sessions),
218                  'focus_session': cross_state.focus_session,
219                  'context_switches': len(cross_state.switch_history),
220                  'attractors': cross_state.cross_session_attractors
221              },
222              'membrane': {
223                  'permeability': self.config.permeability.value
224              }
225          }
226  
227          if self.aha_detector:
228              recent_ahas = self.aha_detector.get_recent_candidates(min_confidence=0.5)
229              state['aha'] = {
230                  'candidates': len(recent_ahas),
231                  'validated': len(self.aha_detector.get_validated_ahas()),
232                  'recent': [
233                      {
234                          'type': a.aha_type.value,
235                          'summary': a.summary[:50],
236                          'confidence': a.confidence
237                      }
238                      for a in recent_ahas[:3]
239                  ]
240              }
241  
242          return state
243  
244      def get_recent_ahas(self, min_confidence: float = 0.5) -> List:
245          """Get recent aha candidates."""
246          if self.aha_detector:
247              return self.aha_detector.get_recent_candidates(min_confidence=min_confidence)
248          return []
249  
250      def compact_daily_note(self, note_filename: str) -> Dict:
251          """
252          Run compaction on a daily note.
253  
254          Uses attention scores to decide what to keep, summarize, or drop.
255          """
256          if not self.daily_compactor:
257              return {'error': 'Compaction not enabled'}
258  
259          note_path = Path(self.config.daily_notes_dir) / note_filename
260          if not note_path.exists():
261              return {'error': f'Note not found: {note_path}'}
262  
263          result = self.daily_compactor.compact_daily_note(str(note_path))
264          return {
265              'original_items': result.original_count,
266              'after_compaction': result.final_count,
267              'decisions': {
268                  'kept': sum(1 for d in result.decisions if d.action == 'keep'),
269                  'summarized': sum(1 for d in result.decisions if d.action == 'summarize'),
270                  'dropped': sum(1 for d in result.decisions if d.action == 'drop')
271              }
272          }
273  
274      def get_daily_summary(self) -> str:
275          """
276          Generate a daily attention summary for the daily note.
277  
278          Returns markdown content suitable for injection into daily note.
279          """
280          state = self.get_state()
281  
282          lines = [
283              "## Attention State",
284              ""
285          ]
286  
287          # Current focus
288          if state['attention']['current_focus']:
289              focus = state['attention']['current_focus']
290              lines.append(f"**Current Focus**: {focus.target_type} ({focus.modality})")
291  
292          # Trajectory
293          if state['attention']['trajectory_confidence'] > 0:
294              lines.append(f"**Trajectory Confidence**: {state['attention']['trajectory_confidence']:.0%}")
295  
296          # Active wells
297          if state['attention']['active_wells']:
298              wells_str = ', '.join(state['attention']['active_wells'])
299              lines.append(f"**Active Gravity Wells**: {wells_str}")
300  
301          # Unresolved
302          if state['attention']['unresolved_count'] > 0:
303              lines.append(f"**Unresolved Items**: {state['attention']['unresolved_count']}")
304  
305          # Cross-session
306          lines.append("")
307          lines.append("### Cross-Session")
308          lines.append(f"- Active sessions: {state['cross_session']['active_sessions']}")
309          lines.append(f"- Context switches: {state['cross_session']['context_switches']}")
310          if state['cross_session']['attractors']:
311              attractors = ', '.join(state['cross_session']['attractors'])
312              lines.append(f"- **Cross-session attractors**: {attractors}")
313  
314          # Aha moments
315          if 'aha' in state and state['aha']['candidates'] > 0:
316              lines.append("")
317              lines.append("### Aha Moments")
318              for aha in state['aha']['recent']:
319                  emoji = "💡" if aha['type'] == 'discovery' else "🔧"
320                  lines.append(f"- {emoji} [{aha['confidence']:.0%}] {aha['summary']}...")
321  
322          return '\n'.join(lines)
323  
324  
325  def create_unified_system(
326      sessions_dir: str,
327      daily_notes_dir: str,
328      permeability: PermeabilityLevel = PermeabilityLevel.ATTRACTORS
329  ) -> UnifiedAttentionSystem:
330      """
331      Create a unified attention system.
332  
333      Args:
334          sessions_dir: Path to session stream files
335          daily_notes_dir: Path to daily notes
336          permeability: How much context crosses between sessions
337  
338      Returns:
339          Configured UnifiedAttentionSystem
340      """
341      config = UnifiedAttentionConfig(
342          sessions_dir=sessions_dir,
343          daily_notes_dir=daily_notes_dir,
344          permeability=permeability
345      )
346      return UnifiedAttentionSystem(config)
347  
348  
349  if __name__ == "__main__":
350      print("=== Unified Attention System ===\n")
351      print("'Attention is all you need'\n")
352  
353      # Example usage
354      print("Creating unified system...")
355      system = create_unified_system(
356          sessions_dir="/Users/rcerf/repos/Sovereign_Estate/daily/sessions/",
357          daily_notes_dir="/Users/rcerf/repos/Sovereign_Estate/daily/"
358      )
359  
360      print("\nProcessing session updates...")
361      stats = system.process_session_updates()
362      print(f"  Active sessions: {stats['active_sessions']}")
363      print(f"  Cross-session events: {stats['cross_events']}")
364      print(f"  Cross-session attractors: {stats['cross_session_attractors']}")
365  
366      print("\nGenerating daily summary...")
367      summary = system.get_daily_summary()
368      print(summary)
369  
370      print("\n=== Unified System Ready ===")