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.
- claude_mpm/agents/INSTRUCTIONS.md +71 -2
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +1 -1
- claude_mpm/agents/templates/engineer.json +1 -1
- claude_mpm/agents/templates/ops.json +1 -1
- claude_mpm/agents/templates/pm.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/research.json +1 -1
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/test_integration.json +112 -0
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/cli/commands/memory.py +575 -25
- claude_mpm/cli/commands/run.py +115 -14
- claude_mpm/cli/parser.py +76 -0
- claude_mpm/constants.py +5 -0
- claude_mpm/core/claude_runner.py +13 -11
- claude_mpm/core/session_manager.py +46 -0
- claude_mpm/core/simple_runner.py +13 -11
- claude_mpm/hooks/claude_hooks/hook_handler.py +2 -26
- claude_mpm/scripts/launch_socketio_dashboard.py +261 -0
- claude_mpm/services/agent_memory_manager.py +264 -23
- claude_mpm/services/memory_builder.py +491 -0
- claude_mpm/services/memory_optimizer.py +619 -0
- claude_mpm/services/memory_router.py +445 -0
- claude_mpm/services/socketio_server.py +389 -1
- claude_mpm-3.3.2.dist-info/METADATA +159 -0
- {claude_mpm-3.2.1.dist-info → claude_mpm-3.3.2.dist-info}/RECORD +31 -29
- claude_mpm/agents/templates/test-integration-agent.md +0 -34
- claude_mpm/core/websocket_handler.py +0 -233
- claude_mpm/services/websocket_server.py +0 -376
- claude_mpm-3.2.1.dist-info/METADATA +0 -432
- {claude_mpm-3.2.1.dist-info → claude_mpm-3.3.2.dist-info}/WHEEL +0 -0
- {claude_mpm-3.2.1.dist-info → claude_mpm-3.3.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.2.1.dist-info → claude_mpm-3.3.2.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
|