claude-mpm 3.2.1__py3-none-any.whl → 3.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. claude_mpm/agents/INSTRUCTIONS.md +71 -2
  2. claude_mpm/agents/templates/data_engineer.json +1 -1
  3. claude_mpm/agents/templates/documentation.json +1 -1
  4. claude_mpm/agents/templates/engineer.json +1 -1
  5. claude_mpm/agents/templates/ops.json +1 -1
  6. claude_mpm/agents/templates/pm.json +1 -1
  7. claude_mpm/agents/templates/qa.json +1 -1
  8. claude_mpm/agents/templates/research.json +1 -1
  9. claude_mpm/agents/templates/security.json +1 -1
  10. claude_mpm/agents/templates/test_integration.json +112 -0
  11. claude_mpm/agents/templates/version_control.json +1 -1
  12. claude_mpm/cli/commands/memory.py +575 -25
  13. claude_mpm/cli/commands/run.py +115 -14
  14. claude_mpm/cli/parser.py +76 -0
  15. claude_mpm/constants.py +5 -0
  16. claude_mpm/core/claude_runner.py +13 -11
  17. claude_mpm/core/session_manager.py +46 -0
  18. claude_mpm/core/simple_runner.py +13 -11
  19. claude_mpm/hooks/claude_hooks/hook_handler.py +2 -26
  20. claude_mpm/scripts/launch_socketio_dashboard.py +261 -0
  21. claude_mpm/services/agent_memory_manager.py +264 -23
  22. claude_mpm/services/memory_builder.py +491 -0
  23. claude_mpm/services/memory_optimizer.py +619 -0
  24. claude_mpm/services/memory_router.py +445 -0
  25. claude_mpm/services/socketio_server.py +389 -1
  26. claude_mpm-3.3.2.dist-info/METADATA +159 -0
  27. {claude_mpm-3.2.1.dist-info → claude_mpm-3.3.2.dist-info}/RECORD +31 -29
  28. claude_mpm/agents/templates/test-integration-agent.md +0 -34
  29. claude_mpm/core/websocket_handler.py +0 -233
  30. claude_mpm/services/websocket_server.py +0 -376
  31. claude_mpm-3.2.1.dist-info/METADATA +0 -432
  32. {claude_mpm-3.2.1.dist-info → claude_mpm-3.3.2.dist-info}/WHEEL +0 -0
  33. {claude_mpm-3.2.1.dist-info → claude_mpm-3.3.2.dist-info}/entry_points.txt +0 -0
  34. {claude_mpm-3.2.1.dist-info → claude_mpm-3.3.2.dist-info}/licenses/LICENSE +0 -0
  35. {claude_mpm-3.2.1.dist-info → claude_mpm-3.3.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env python3
2
+ """Socket.IO Dashboard Launcher for Claude MPM.
3
+
4
+ WHY: This script provides a streamlined solution for launching the Socket.IO
5
+ monitoring dashboard using only the Python Socket.IO server implementation.
6
+ It handles server startup, dashboard creation, and browser opening.
7
+
8
+ DESIGN DECISION: Uses only python-socketio and aiohttp for a clean,
9
+ Node.js-free implementation. This simplifies deployment and reduces
10
+ dependencies while maintaining full functionality.
11
+
12
+ The script handles:
13
+ 1. Python Socket.IO server startup
14
+ 2. Dashboard HTML creation and serving
15
+ 3. Browser opening with proper URL construction
16
+ 4. Background/daemon mode operation
17
+ 5. Graceful error handling and user feedback
18
+ """
19
+
20
+ import argparse
21
+ import os
22
+ import sys
23
+ import time
24
+ import webbrowser
25
+ import signal
26
+ from pathlib import Path
27
+ from typing import Optional
28
+
29
+ # Get script directory for relative paths
30
+ SCRIPT_DIR = Path(__file__).parent
31
+ PROJECT_ROOT = SCRIPT_DIR.parent.parent.parent # Go up to project root from src/claude_mpm/scripts/
32
+
33
+ def check_python_dependencies() -> bool:
34
+ """Check if Python Socket.IO dependencies are available.
35
+
36
+ WHY: We need python-socketio and aiohttp packages for the server.
37
+ This function validates the environment and provides clear feedback.
38
+
39
+ Returns:
40
+ bool: True if Python dependencies are ready, False otherwise
41
+ """
42
+ try:
43
+ import socketio
44
+ import aiohttp
45
+ socketio_version = getattr(socketio, '__version__', 'unknown')
46
+ aiohttp_version = getattr(aiohttp, '__version__', 'unknown')
47
+ print(f"✓ python-socketio v{socketio_version} detected")
48
+ print(f"✓ aiohttp v{aiohttp_version} detected")
49
+ return True
50
+ except ImportError as e:
51
+ print(f"❌ Required Python packages missing: {e}")
52
+ print(" Install with: pip install python-socketio aiohttp")
53
+ return False
54
+
55
+
56
+
57
+ def check_dashboard_availability(port: int):
58
+ """Check if the modular dashboard is available.
59
+
60
+ WHY: The new architecture uses a modular dashboard served by the Socket.IO server
61
+ instead of creating static HTML files. This validates the proper dashboard exists.
62
+
63
+ Args:
64
+ port: Port number for the Socket.IO server
65
+ """
66
+ # Check if new modular dashboard is available
67
+ web_templates_dir = PROJECT_ROOT / "src" / "claude_mpm" / "web" / "templates"
68
+ modular_dashboard = web_templates_dir / "index.html"
69
+ if modular_dashboard.exists():
70
+ print(f"✓ Modular dashboard found at {modular_dashboard}")
71
+ return True
72
+ else:
73
+ print(f"⚠️ Modular dashboard not found at {modular_dashboard}")
74
+ print(f" Expected path: {modular_dashboard}")
75
+ return False
76
+
77
+ def check_server_running(port: int) -> bool:
78
+ """Check if a Socket.IO server is already running on the specified port.
79
+
80
+ WHY: We want to avoid starting multiple servers on the same port
81
+ and provide clear feedback to users about existing servers.
82
+
83
+ Args:
84
+ port: Port number to check
85
+
86
+ Returns:
87
+ bool: True if server is running, False otherwise
88
+ """
89
+ try:
90
+ import socket
91
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
92
+ s.settimeout(1)
93
+ result = s.connect_ex(('127.0.0.1', port))
94
+ if result == 0:
95
+ print(f"✓ Socket.IO server already running on port {port}")
96
+ return True
97
+ except Exception:
98
+ pass
99
+
100
+ return False
101
+
102
+ def start_python_server(port: int, daemon: bool = False) -> Optional:
103
+ """Start the Python Socket.IO server.
104
+
105
+ WHY: Uses python-socketio and aiohttp for a clean, Node.js-free
106
+ implementation that handles all Socket.IO functionality.
107
+
108
+ Args:
109
+ port: Port number for the server
110
+ daemon: Whether to run in background mode
111
+
112
+ Returns:
113
+ Thread object if successful, None otherwise
114
+ """
115
+ try:
116
+ # Import the existing Python Socket.IO server
117
+ sys.path.insert(0, str(PROJECT_ROOT / "src"))
118
+ from claude_mpm.services.socketio_server import SocketIOServer
119
+
120
+ server = SocketIOServer(port=port)
121
+
122
+ if daemon:
123
+ # Start in background thread
124
+ server.start()
125
+ print(f"🚀 Python Socket.IO server started on port {port}")
126
+ return server.thread
127
+ else:
128
+ # Start and block
129
+ print(f"🚀 Starting Python Socket.IO server on port {port}")
130
+ server.start()
131
+
132
+ # Keep alive until interrupted
133
+ try:
134
+ while server.running:
135
+ time.sleep(1)
136
+ except KeyboardInterrupt:
137
+ print("\\n🛑 Shutting down Python server...")
138
+ server.stop()
139
+
140
+ return None
141
+
142
+ except Exception as e:
143
+ print(f"❌ Failed to start Python server: {e}")
144
+ return None
145
+
146
+ def open_dashboard(port: int, no_browser: bool = False):
147
+ """Open the Socket.IO dashboard in browser.
148
+
149
+ WHY: Users need easy access to the monitoring dashboard. This function
150
+ handles URL construction and browser opening with fallback options.
151
+ Now uses the new modular dashboard location.
152
+
153
+ Args:
154
+ port: Port number for the Socket.IO server
155
+ no_browser: Skip browser opening if True
156
+ """
157
+ if no_browser:
158
+ print(f"📊 Dashboard available at: http://localhost:{port}/dashboard")
159
+ return
160
+
161
+ dashboard_url = f"http://localhost:{port}/dashboard?autoconnect=true&port={port}"
162
+
163
+ try:
164
+ print(f"🌐 Opening dashboard: {dashboard_url}")
165
+ webbrowser.open(dashboard_url)
166
+
167
+ except Exception as e:
168
+ print(f"⚠️ Failed to open browser automatically: {e}")
169
+ print(f"📊 Dashboard: {dashboard_url}")
170
+
171
+ def cleanup_handler(signum, frame):
172
+ """Handle cleanup on shutdown signals.
173
+
174
+ WHY: Proper cleanup ensures sockets are closed and resources freed
175
+ when the script is terminated.
176
+ """
177
+ print("\\n🛑 Shutting down Socket.IO launcher...")
178
+ sys.exit(0)
179
+
180
+ def main():
181
+ """Main entry point for the Socket.IO dashboard launcher.
182
+
183
+ WHY: This orchestrates the entire launch process, from dependency checking
184
+ to server startup and dashboard opening, with comprehensive error handling.
185
+ """
186
+ parser = argparse.ArgumentParser(
187
+ description="Launch Socket.IO dashboard for Claude MPM monitoring",
188
+ formatter_class=argparse.RawDescriptionHelpFormatter,
189
+ epilog='''
190
+ Examples:
191
+ python launch_socketio_dashboard.py # Start with default settings
192
+ python launch_socketio_dashboard.py --port 3000 # Use specific port
193
+ python launch_socketio_dashboard.py --daemon # Run in background
194
+ python launch_socketio_dashboard.py --no-browser # Don't open browser
195
+ python launch_socketio_dashboard.py --setup-only # Just create files
196
+ '''
197
+ )
198
+
199
+ parser.add_argument('--port', type=int, default=3000,
200
+ help='Socket.IO server port (default: 3000)')
201
+ parser.add_argument('--daemon', action='store_true',
202
+ help='Run server in background mode')
203
+ parser.add_argument('--no-browser', action='store_true',
204
+ help='Skip opening browser automatically')
205
+ parser.add_argument('--setup-only', action='store_true',
206
+ help='Create necessary files without starting server')
207
+
208
+ args = parser.parse_args()
209
+
210
+ # Setup signal handlers
211
+ signal.signal(signal.SIGINT, cleanup_handler)
212
+ signal.signal(signal.SIGTERM, cleanup_handler)
213
+
214
+ print("🚀 Claude MPM Socket.IO Dashboard Launcher")
215
+ print("=" * 50)
216
+
217
+ # Check dashboard availability (modular dashboard)
218
+ check_dashboard_availability(args.port)
219
+
220
+ if args.setup_only:
221
+ # Just setup files, don't start server
222
+ print("📁 Setup complete - files created")
223
+ return
224
+
225
+ # Check if server is already running
226
+ if check_server_running(args.port):
227
+ print(f"✅ Using existing server on port {args.port}")
228
+ open_dashboard(args.port, args.no_browser)
229
+ return
230
+
231
+ # Check Python dependencies
232
+ if not check_python_dependencies():
233
+ print("❌ Required Python packages not available")
234
+ sys.exit(1)
235
+
236
+ # Start Python Socket.IO server
237
+ print("🟢 Using Python Socket.IO server")
238
+
239
+ try:
240
+ server_thread = start_python_server(args.port, args.daemon)
241
+
242
+ if server_thread or not args.daemon:
243
+ # Server started or is starting
244
+ time.sleep(2) # Give server time to start
245
+ open_dashboard(args.port, args.no_browser)
246
+
247
+ if args.daemon and server_thread:
248
+ print(f"🔄 Python server running in background")
249
+ print(f" Dashboard: http://localhost:{args.port}/dashboard")
250
+ else:
251
+ print("❌ Failed to start Socket.IO server")
252
+ sys.exit(1)
253
+
254
+ except KeyboardInterrupt:
255
+ print("\\n✅ Socket.IO launcher stopped")
256
+ except Exception as e:
257
+ print(f"❌ Launcher error: {e}")
258
+ sys.exit(1)
259
+
260
+ if __name__ == "__main__":
261
+ main()
@@ -27,7 +27,7 @@ import logging
27
27
  from claude_mpm.core import LoggerMixin
28
28
  from claude_mpm.core.config import Config
29
29
  from claude_mpm.utils.paths import PathResolver
30
- from claude_mpm.services.websocket_server import get_websocket_server
30
+ # Socket.IO notifications are optional - we'll skip them if server is not available
31
31
 
32
32
 
33
33
  class AgentMemoryManager(LoggerMixin):
@@ -164,15 +164,7 @@ class AgentMemoryManager(LoggerMixin):
164
164
  try:
165
165
  content = memory_file.read_text(encoding='utf-8')
166
166
 
167
- # Emit WebSocket event for memory loaded
168
- try:
169
- ws_server = get_websocket_server()
170
- file_size = len(content.encode('utf-8'))
171
- # Count sections by looking for lines starting with ##
172
- sections_count = sum(1 for line in content.split('\n') if line.startswith('## '))
173
- ws_server.memory_loaded(agent_id, file_size, sections_count)
174
- except Exception as ws_error:
175
- self.logger.debug(f"WebSocket notification failed: {ws_error}")
167
+ # Socket.IO notifications removed - memory manager works independently
176
168
 
177
169
  return self._validate_and_repair(content, agent_id)
178
170
  except Exception as e:
@@ -241,13 +233,7 @@ class AgentMemoryManager(LoggerMixin):
241
233
  section = section_mapping.get(learning_type, 'Recent Learnings')
242
234
  success = self.update_agent_memory(agent_id, section, content)
243
235
 
244
- # Emit WebSocket event for memory updated
245
- if success:
246
- try:
247
- ws_server = get_websocket_server()
248
- ws_server.memory_updated(agent_id, learning_type, content, section)
249
- except Exception as ws_error:
250
- self.logger.debug(f"WebSocket notification failed: {ws_error}")
236
+ # Socket.IO notifications removed - memory manager works independently
251
237
 
252
238
  return success
253
239
 
@@ -323,12 +309,7 @@ class AgentMemoryManager(LoggerMixin):
323
309
  memory_file.write_text(template, encoding='utf-8')
324
310
  self.logger.info(f"Created default memory file for {agent_id}")
325
311
 
326
- # Emit WebSocket event for memory created
327
- try:
328
- ws_server = get_websocket_server()
329
- ws_server.memory_created(agent_id, "default")
330
- except Exception as ws_error:
331
- self.logger.debug(f"WebSocket notification failed: {ws_error}")
312
+ # Socket.IO notifications removed - memory manager works independently
332
313
  except Exception as e:
333
314
  self.logger.error(f"Error saving default memory for {agent_id}: {e}")
334
315
 
@@ -609,6 +590,266 @@ class AgentMemoryManager(LoggerMixin):
609
590
  self.logger.error(f"Error saving memory for {agent_id}: {e}")
610
591
  return False
611
592
 
593
+ def optimize_memory(self, agent_id: Optional[str] = None) -> Dict[str, Any]:
594
+ """Optimize agent memory by consolidating/cleaning memories.
595
+
596
+ WHY: Over time, memory files accumulate redundant or outdated information.
597
+ This method delegates to the memory optimizer service to clean up and
598
+ consolidate memories while preserving important information.
599
+
600
+ Args:
601
+ agent_id: Optional specific agent ID. If None, optimizes all agents.
602
+
603
+ Returns:
604
+ Dict containing optimization results and statistics
605
+ """
606
+ try:
607
+ from claude_mpm.services.memory_optimizer import MemoryOptimizer
608
+ optimizer = MemoryOptimizer(self.config)
609
+
610
+ if agent_id:
611
+ result = optimizer.optimize_agent_memory(agent_id)
612
+ self.logger.info(f"Optimized memory for agent: {agent_id}")
613
+ else:
614
+ result = optimizer.optimize_all_memories()
615
+ self.logger.info("Optimized all agent memories")
616
+
617
+ return result
618
+ except Exception as e:
619
+ self.logger.error(f"Error optimizing memory: {e}")
620
+ return {"success": False, "error": str(e)}
621
+
622
+ def build_memories_from_docs(self, force_rebuild: bool = False) -> Dict[str, Any]:
623
+ """Build agent memories from project documentation.
624
+
625
+ WHY: Project documentation contains valuable knowledge that should be
626
+ extracted and assigned to appropriate agents for better context awareness.
627
+
628
+ Args:
629
+ force_rebuild: If True, rebuilds even if docs haven't changed
630
+
631
+ Returns:
632
+ Dict containing build results and statistics
633
+ """
634
+ try:
635
+ from claude_mpm.services.memory_builder import MemoryBuilder
636
+ builder = MemoryBuilder(self.config)
637
+
638
+ result = builder.build_from_documentation(force_rebuild)
639
+ self.logger.info("Built memories from documentation")
640
+
641
+ return result
642
+ except Exception as e:
643
+ self.logger.error(f"Error building memories from docs: {e}")
644
+ return {"success": False, "error": str(e)}
645
+
646
+ def route_memory_command(self, content: str, context: Optional[Dict] = None) -> Dict[str, Any]:
647
+ """Route memory command to appropriate agent via PM delegation.
648
+
649
+ WHY: Memory commands like "remember this for next time" need to be analyzed
650
+ to determine which agent should store the information. This method provides
651
+ routing logic for PM agent delegation.
652
+
653
+ Args:
654
+ content: The content to be remembered
655
+ context: Optional context for routing decisions
656
+
657
+ Returns:
658
+ Dict containing routing decision and reasoning
659
+ """
660
+ try:
661
+ from claude_mpm.services.memory_router import MemoryRouter
662
+ router = MemoryRouter(self.config)
663
+
664
+ routing_result = router.analyze_and_route(content, context)
665
+ self.logger.debug(f"Routed memory command: {routing_result['target_agent']}")
666
+
667
+ return routing_result
668
+ except Exception as e:
669
+ self.logger.error(f"Error routing memory command: {e}")
670
+ return {"success": False, "error": str(e)}
671
+
672
+ def get_memory_status(self) -> Dict[str, Any]:
673
+ """Get comprehensive memory system status.
674
+
675
+ WHY: Provides detailed overview of memory system health, file sizes,
676
+ optimization opportunities, and agent-specific statistics for monitoring
677
+ and maintenance purposes.
678
+
679
+ Returns:
680
+ Dict containing comprehensive memory system status
681
+ """
682
+ try:
683
+ status = {
684
+ "system_enabled": self.memory_enabled,
685
+ "auto_learning": self.auto_learning,
686
+ "memory_directory": str(self.memories_dir),
687
+ "total_agents": 0,
688
+ "total_size_kb": 0,
689
+ "agents": {},
690
+ "optimization_opportunities": [],
691
+ "system_health": "healthy"
692
+ }
693
+
694
+ if not self.memories_dir.exists():
695
+ status["system_health"] = "no_memory_dir"
696
+ return status
697
+
698
+ memory_files = list(self.memories_dir.glob("*_agent.md"))
699
+ status["total_agents"] = len(memory_files)
700
+
701
+ total_size = 0
702
+ for file_path in memory_files:
703
+ stat = file_path.stat()
704
+ size_kb = stat.st_size / 1024
705
+ total_size += stat.st_size
706
+
707
+ agent_id = file_path.stem.replace('_agent', '')
708
+ limits = self._get_agent_limits(agent_id)
709
+
710
+ # Analyze file content
711
+ try:
712
+ content = file_path.read_text()
713
+ section_count = len([line for line in content.splitlines() if line.startswith('## ')])
714
+ learning_count = len([line for line in content.splitlines() if line.strip().startswith('- ')])
715
+
716
+ agent_status = {
717
+ "size_kb": round(size_kb, 2),
718
+ "size_limit_kb": limits['max_file_size_kb'],
719
+ "size_utilization": min(100, round((size_kb / limits['max_file_size_kb']) * 100, 1)),
720
+ "sections": section_count,
721
+ "items": learning_count,
722
+ "last_modified": datetime.fromtimestamp(stat.st_mtime).isoformat(),
723
+ "auto_learning": self._get_agent_auto_learning(agent_id)
724
+ }
725
+
726
+ # Check for optimization opportunities
727
+ if size_kb > limits['max_file_size_kb'] * 0.8:
728
+ status["optimization_opportunities"].append(f"{agent_id}: High memory usage ({size_kb:.1f}KB)")
729
+
730
+ if section_count > limits['max_sections'] * 0.8:
731
+ status["optimization_opportunities"].append(f"{agent_id}: Many sections ({section_count})")
732
+
733
+ status["agents"][agent_id] = agent_status
734
+
735
+ except Exception as e:
736
+ status["agents"][agent_id] = {"error": str(e)}
737
+
738
+ status["total_size_kb"] = round(total_size / 1024, 2)
739
+
740
+ # Determine overall system health
741
+ if len(status["optimization_opportunities"]) > 3:
742
+ status["system_health"] = "needs_optimization"
743
+ elif status["total_size_kb"] > 100: # More than 100KB total
744
+ status["system_health"] = "high_usage"
745
+
746
+ return status
747
+
748
+ except Exception as e:
749
+ self.logger.error(f"Error getting memory status: {e}")
750
+ return {"success": False, "error": str(e)}
751
+
752
+ def cross_reference_memories(self, query: Optional[str] = None) -> Dict[str, Any]:
753
+ """Find common patterns and cross-references across agent memories.
754
+
755
+ WHY: Different agents may have learned similar or related information.
756
+ Cross-referencing helps identify knowledge gaps, redundancies, and
757
+ opportunities for knowledge sharing between agents.
758
+
759
+ Args:
760
+ query: Optional query to filter cross-references
761
+
762
+ Returns:
763
+ Dict containing cross-reference analysis results
764
+ """
765
+ try:
766
+ cross_refs = {
767
+ "common_patterns": [],
768
+ "knowledge_gaps": [],
769
+ "redundancies": [],
770
+ "agent_correlations": {},
771
+ "query_matches": [] if query else None
772
+ }
773
+
774
+ if not self.memories_dir.exists():
775
+ return cross_refs
776
+
777
+ memory_files = list(self.memories_dir.glob("*_agent.md"))
778
+ agent_memories = {}
779
+
780
+ # Load all agent memories
781
+ for file_path in memory_files:
782
+ agent_id = file_path.stem.replace('_agent', '')
783
+ try:
784
+ content = file_path.read_text()
785
+ agent_memories[agent_id] = content
786
+ except Exception as e:
787
+ self.logger.warning(f"Error reading memory for {agent_id}: {e}")
788
+ continue
789
+
790
+ # Find common patterns across agents
791
+ all_lines = []
792
+ agent_lines = {}
793
+
794
+ for agent_id, content in agent_memories.items():
795
+ lines = [line.strip() for line in content.splitlines()
796
+ if line.strip().startswith('- ')]
797
+ agent_lines[agent_id] = lines
798
+ all_lines.extend([(line, agent_id) for line in lines])
799
+
800
+ # Look for similar content (basic similarity check)
801
+ line_counts = {}
802
+ for line, agent_id in all_lines:
803
+ # Normalize line for comparison
804
+ normalized = line.lower().replace('- ', '').strip()
805
+ if len(normalized) > 20: # Only check substantial lines
806
+ if normalized not in line_counts:
807
+ line_counts[normalized] = []
808
+ line_counts[normalized].append(agent_id)
809
+
810
+ # Find patterns appearing in multiple agents
811
+ for line, agents in line_counts.items():
812
+ if len(set(agents)) > 1: # Appears in multiple agents
813
+ cross_refs["common_patterns"].append({
814
+ "pattern": line[:100] + "..." if len(line) > 100 else line,
815
+ "agents": list(set(agents)),
816
+ "count": len(agents)
817
+ })
818
+
819
+ # Query-specific matches
820
+ if query:
821
+ query_lower = query.lower()
822
+ for agent_id, content in agent_memories.items():
823
+ matches = []
824
+ for line in content.splitlines():
825
+ if query_lower in line.lower():
826
+ matches.append(line.strip())
827
+
828
+ if matches:
829
+ cross_refs["query_matches"].append({
830
+ "agent": agent_id,
831
+ "matches": matches[:5] # Limit to first 5 matches
832
+ })
833
+
834
+ # Calculate agent correlations (agents with similar knowledge domains)
835
+ for agent_a in agent_memories:
836
+ for agent_b in agent_memories:
837
+ if agent_a < agent_b: # Avoid duplicates
838
+ common_count = len([
839
+ line for line in line_counts.values()
840
+ if agent_a in line and agent_b in line
841
+ ])
842
+
843
+ if common_count > 0:
844
+ correlation_key = f"{agent_a}+{agent_b}"
845
+ cross_refs["agent_correlations"][correlation_key] = common_count
846
+
847
+ return cross_refs
848
+
849
+ except Exception as e:
850
+ self.logger.error(f"Error cross-referencing memories: {e}")
851
+ return {"success": False, "error": str(e)}
852
+
612
853
  def _ensure_memories_directory(self):
613
854
  """Ensure memories directory exists with README.
614
855