claude-mpm 4.1.2__py3-none-any.whl → 4.1.4__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/VERSION +1 -1
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +16 -19
- claude_mpm/agents/MEMORY.md +21 -49
- claude_mpm/agents/templates/OPTIMIZATION_REPORT.md +156 -0
- claude_mpm/agents/templates/api_qa.json +36 -116
- claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +42 -9
- claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +29 -6
- claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +34 -6
- claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +41 -9
- claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +30 -8
- claude_mpm/agents/templates/backup/research_agent_2025011_234551.json +2 -2
- claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +29 -6
- claude_mpm/agents/templates/backup/research_memory_efficient.json +2 -2
- claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +41 -9
- claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +23 -7
- claude_mpm/agents/templates/code_analyzer.json +18 -36
- claude_mpm/agents/templates/data_engineer.json +43 -14
- claude_mpm/agents/templates/documentation.json +55 -74
- claude_mpm/agents/templates/engineer.json +57 -40
- claude_mpm/agents/templates/imagemagick.json +7 -2
- claude_mpm/agents/templates/memory_manager.json +1 -1
- claude_mpm/agents/templates/ops.json +36 -4
- claude_mpm/agents/templates/project_organizer.json +23 -71
- claude_mpm/agents/templates/qa.json +34 -2
- claude_mpm/agents/templates/refactoring_engineer.json +9 -5
- claude_mpm/agents/templates/research.json +36 -4
- claude_mpm/agents/templates/security.json +29 -2
- claude_mpm/agents/templates/ticketing.json +3 -3
- claude_mpm/agents/templates/vercel_ops_agent.json +2 -2
- claude_mpm/agents/templates/version_control.json +28 -2
- claude_mpm/agents/templates/web_qa.json +38 -151
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/commands/agent_manager.py +221 -1
- claude_mpm/cli/commands/agents.py +556 -1009
- claude_mpm/cli/commands/memory.py +248 -927
- claude_mpm/cli/commands/run.py +139 -484
- claude_mpm/cli/parsers/agent_manager_parser.py +34 -0
- claude_mpm/cli/startup_logging.py +76 -0
- claude_mpm/core/agent_registry.py +6 -10
- claude_mpm/core/framework_loader.py +205 -595
- claude_mpm/core/log_manager.py +49 -1
- claude_mpm/core/logging_config.py +2 -4
- claude_mpm/hooks/claude_hooks/event_handlers.py +7 -117
- claude_mpm/hooks/claude_hooks/hook_handler.py +91 -755
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +1040 -0
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +347 -0
- claude_mpm/hooks/claude_hooks/services/__init__.py +13 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +190 -0
- claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +282 -0
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +42 -454
- claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
- claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
- claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
- claude_mpm/services/agents/memory/agent_memory_manager.py +42 -508
- claude_mpm/services/agents/memory/memory_categorization_service.py +165 -0
- claude_mpm/services/agents/memory/memory_file_service.py +103 -0
- claude_mpm/services/agents/memory/memory_format_service.py +201 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +99 -0
- claude_mpm/services/agents/registry/__init__.py +1 -1
- claude_mpm/services/cli/__init__.py +18 -0
- claude_mpm/services/cli/agent_cleanup_service.py +407 -0
- claude_mpm/services/cli/agent_dependency_service.py +395 -0
- claude_mpm/services/cli/agent_listing_service.py +463 -0
- claude_mpm/services/cli/agent_output_formatter.py +605 -0
- claude_mpm/services/cli/agent_validation_service.py +589 -0
- claude_mpm/services/cli/dashboard_launcher.py +424 -0
- claude_mpm/services/cli/memory_crud_service.py +617 -0
- claude_mpm/services/cli/memory_output_formatter.py +604 -0
- claude_mpm/services/cli/session_manager.py +513 -0
- claude_mpm/services/cli/socketio_manager.py +498 -0
- claude_mpm/services/cli/startup_checker.py +370 -0
- claude_mpm/services/core/cache_manager.py +311 -0
- claude_mpm/services/core/memory_manager.py +637 -0
- claude_mpm/services/core/path_resolver.py +498 -0
- claude_mpm/services/core/service_container.py +520 -0
- claude_mpm/services/core/service_interfaces.py +436 -0
- claude_mpm/services/diagnostics/checks/agent_check.py +65 -19
- claude_mpm/services/memory/router.py +116 -10
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/RECORD +86 -55
- claude_mpm/cli/commands/run_config_checker.py +0 -159
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.4.dist-info}/top_level.txt +0 -0
|
@@ -23,7 +23,6 @@ following the naming convention: {agent_id}_memories.md
|
|
|
23
23
|
|
|
24
24
|
import logging
|
|
25
25
|
import os
|
|
26
|
-
from datetime import datetime
|
|
27
26
|
from typing import Any, Dict, List, Optional, Tuple
|
|
28
27
|
|
|
29
28
|
from claude_mpm.core.config import Config
|
|
@@ -31,6 +30,10 @@ from claude_mpm.core.interfaces import MemoryServiceInterface
|
|
|
31
30
|
from claude_mpm.core.unified_paths import get_path_manager
|
|
32
31
|
|
|
33
32
|
from .content_manager import MemoryContentManager
|
|
33
|
+
from .memory_categorization_service import MemoryCategorizationService
|
|
34
|
+
from .memory_file_service import MemoryFileService
|
|
35
|
+
from .memory_format_service import MemoryFormatService
|
|
36
|
+
from .memory_limits_service import MemoryLimitsService
|
|
34
37
|
from .template_generator import MemoryTemplateGenerator
|
|
35
38
|
|
|
36
39
|
|
|
@@ -83,11 +86,15 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
83
86
|
# Primary memories_dir points to project
|
|
84
87
|
self.memories_dir = self.project_memories_dir
|
|
85
88
|
|
|
86
|
-
#
|
|
87
|
-
self.
|
|
89
|
+
# Initialize services
|
|
90
|
+
self.file_service = MemoryFileService(self.memories_dir)
|
|
91
|
+
self.limits_service = MemoryLimitsService(self.config)
|
|
92
|
+
self.memory_limits = self.limits_service.memory_limits
|
|
93
|
+
self.format_service = MemoryFormatService()
|
|
94
|
+
self.categorization_service = MemoryCategorizationService()
|
|
88
95
|
|
|
89
|
-
#
|
|
90
|
-
self.
|
|
96
|
+
# Ensure project directory exists
|
|
97
|
+
self.file_service.ensure_memories_directory()
|
|
91
98
|
|
|
92
99
|
# Initialize component services
|
|
93
100
|
self.template_generator = MemoryTemplateGenerator(
|
|
@@ -114,132 +121,6 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
114
121
|
|
|
115
122
|
return self._logger_instance
|
|
116
123
|
|
|
117
|
-
def _init_memory_limits(self):
|
|
118
|
-
"""Initialize memory limits from configuration.
|
|
119
|
-
|
|
120
|
-
WHY: Allows configuration-driven memory limits instead of hardcoded values.
|
|
121
|
-
Supports agent-specific overrides for different memory requirements.
|
|
122
|
-
"""
|
|
123
|
-
# Check if memory system is enabled
|
|
124
|
-
self.memory_enabled = self.config.get("memory.enabled", True)
|
|
125
|
-
self.auto_learning = self.config.get(
|
|
126
|
-
"memory.auto_learning", True
|
|
127
|
-
) # Changed default to True
|
|
128
|
-
|
|
129
|
-
# Load default limits from configuration
|
|
130
|
-
config_limits = self.config.get("memory.limits", {})
|
|
131
|
-
self.memory_limits = {
|
|
132
|
-
"max_file_size_kb": config_limits.get(
|
|
133
|
-
"default_size_kb", self.DEFAULT_MEMORY_LIMITS["max_file_size_kb"]
|
|
134
|
-
),
|
|
135
|
-
"max_items": config_limits.get(
|
|
136
|
-
"max_items", self.DEFAULT_MEMORY_LIMITS["max_items"]
|
|
137
|
-
),
|
|
138
|
-
"max_line_length": config_limits.get(
|
|
139
|
-
"max_line_length", self.DEFAULT_MEMORY_LIMITS["max_line_length"]
|
|
140
|
-
),
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
# Load agent-specific overrides
|
|
144
|
-
self.agent_overrides = self.config.get("memory.agent_overrides", {})
|
|
145
|
-
|
|
146
|
-
def _get_agent_limits(self, agent_id: str) -> Dict[str, Any]:
|
|
147
|
-
"""Get memory limits for specific agent, including overrides.
|
|
148
|
-
|
|
149
|
-
WHY: Different agents may need different memory capacities. Research agents
|
|
150
|
-
might need larger memory for comprehensive findings, while simple agents
|
|
151
|
-
can work with smaller limits.
|
|
152
|
-
|
|
153
|
-
Args:
|
|
154
|
-
agent_id: The agent identifier
|
|
155
|
-
|
|
156
|
-
Returns:
|
|
157
|
-
Dict containing the effective limits for this agent
|
|
158
|
-
"""
|
|
159
|
-
# Start with default limits
|
|
160
|
-
limits = self.memory_limits.copy()
|
|
161
|
-
|
|
162
|
-
# Apply agent-specific overrides if they exist
|
|
163
|
-
if agent_id in self.agent_overrides:
|
|
164
|
-
overrides = self.agent_overrides[agent_id]
|
|
165
|
-
if "size_kb" in overrides:
|
|
166
|
-
limits["max_file_size_kb"] = overrides["size_kb"]
|
|
167
|
-
|
|
168
|
-
return limits
|
|
169
|
-
|
|
170
|
-
def _get_agent_auto_learning(self, agent_id: str) -> bool:
|
|
171
|
-
"""Check if auto-learning is enabled for specific agent.
|
|
172
|
-
|
|
173
|
-
Args:
|
|
174
|
-
agent_id: The agent identifier
|
|
175
|
-
|
|
176
|
-
Returns:
|
|
177
|
-
bool: True if auto-learning is enabled for this agent
|
|
178
|
-
"""
|
|
179
|
-
# Check agent-specific override first
|
|
180
|
-
if agent_id in self.agent_overrides:
|
|
181
|
-
return self.agent_overrides[agent_id].get(
|
|
182
|
-
"auto_learning", self.auto_learning
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
# Fall back to global setting
|
|
186
|
-
return self.auto_learning
|
|
187
|
-
|
|
188
|
-
def _get_memory_file_with_migration(self, directory: Path, agent_id: str) -> Path:
|
|
189
|
-
"""Get memory file path, migrating from old naming if needed.
|
|
190
|
-
|
|
191
|
-
WHY: Supports backward compatibility by automatically migrating from
|
|
192
|
-
the old {agent_id}_agent.md and {agent_id}.md formats to the new {agent_id}_memories.md format.
|
|
193
|
-
|
|
194
|
-
Args:
|
|
195
|
-
directory: Directory containing memory files
|
|
196
|
-
agent_id: The agent identifier
|
|
197
|
-
|
|
198
|
-
Returns:
|
|
199
|
-
Path: Path to the memory file (may not exist)
|
|
200
|
-
"""
|
|
201
|
-
new_file = directory / f"{agent_id}_memories.md"
|
|
202
|
-
# Support migration from both old formats
|
|
203
|
-
old_file_agent = directory / f"{agent_id}_agent.md"
|
|
204
|
-
old_file_simple = directory / f"{agent_id}.md"
|
|
205
|
-
|
|
206
|
-
# Migrate from old formats if needed
|
|
207
|
-
if not new_file.exists():
|
|
208
|
-
# Try migrating from {agent_id}_agent.md first
|
|
209
|
-
if old_file_agent.exists():
|
|
210
|
-
try:
|
|
211
|
-
content = old_file_agent.read_text(encoding="utf-8")
|
|
212
|
-
new_file.write_text(content, encoding="utf-8")
|
|
213
|
-
|
|
214
|
-
# Delete old file for all agents
|
|
215
|
-
old_file_agent.unlink()
|
|
216
|
-
self.logger.info(
|
|
217
|
-
f"Migrated memory file from {old_file_agent.name} to {new_file.name}"
|
|
218
|
-
)
|
|
219
|
-
except Exception as e:
|
|
220
|
-
self.logger.error(
|
|
221
|
-
f"Failed to migrate memory file for {agent_id}: {e}"
|
|
222
|
-
)
|
|
223
|
-
return old_file_agent
|
|
224
|
-
# Try migrating from {agent_id}.md
|
|
225
|
-
elif old_file_simple.exists():
|
|
226
|
-
try:
|
|
227
|
-
content = old_file_simple.read_text(encoding="utf-8")
|
|
228
|
-
new_file.write_text(content, encoding="utf-8")
|
|
229
|
-
|
|
230
|
-
# Delete old file for all agents
|
|
231
|
-
old_file_simple.unlink()
|
|
232
|
-
self.logger.info(
|
|
233
|
-
f"Migrated memory file from {old_file_simple.name} to {new_file.name}"
|
|
234
|
-
)
|
|
235
|
-
except Exception as e:
|
|
236
|
-
self.logger.error(
|
|
237
|
-
f"Failed to migrate memory file for {agent_id}: {e}"
|
|
238
|
-
)
|
|
239
|
-
return old_file_simple
|
|
240
|
-
|
|
241
|
-
return new_file
|
|
242
|
-
|
|
243
124
|
def load_agent_memory(self, agent_id: str) -> str:
|
|
244
125
|
"""Load agent memory file content from project directory.
|
|
245
126
|
|
|
@@ -254,7 +135,7 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
254
135
|
str: The memory file content, creating default if doesn't exist
|
|
255
136
|
"""
|
|
256
137
|
# All agents use project directory
|
|
257
|
-
project_memory_file = self.
|
|
138
|
+
project_memory_file = self.file_service.get_memory_file_with_migration(
|
|
258
139
|
self.project_memories_dir, agent_id
|
|
259
140
|
)
|
|
260
141
|
|
|
@@ -326,7 +207,7 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
326
207
|
str: The project-specific memory template content
|
|
327
208
|
"""
|
|
328
209
|
# Get limits for this agent
|
|
329
|
-
limits = self.
|
|
210
|
+
limits = self.limits_service.get_agent_limits(agent_id)
|
|
330
211
|
|
|
331
212
|
# Delegate to template generator
|
|
332
213
|
template = self.template_generator.create_default_memory(agent_id, limits)
|
|
@@ -343,37 +224,6 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
343
224
|
|
|
344
225
|
return template
|
|
345
226
|
|
|
346
|
-
def _save_memory_file(self, agent_id: str, content: str) -> bool:
|
|
347
|
-
"""Save memory content to file.
|
|
348
|
-
|
|
349
|
-
WHY: Memory updates need to be persisted atomically to prevent corruption
|
|
350
|
-
and ensure learnings are preserved across agent invocations.
|
|
351
|
-
|
|
352
|
-
Args:
|
|
353
|
-
agent_id: Agent identifier
|
|
354
|
-
content: Content to save
|
|
355
|
-
|
|
356
|
-
Returns:
|
|
357
|
-
bool: True if save succeeded
|
|
358
|
-
"""
|
|
359
|
-
try:
|
|
360
|
-
# All agents save to project directory
|
|
361
|
-
target_dir = self.project_memories_dir
|
|
362
|
-
|
|
363
|
-
# Ensure directory exists
|
|
364
|
-
target_dir.mkdir(parents=True, exist_ok=True)
|
|
365
|
-
|
|
366
|
-
memory_file = target_dir / f"{agent_id}_memories.md"
|
|
367
|
-
memory_file.write_text(content, encoding="utf-8")
|
|
368
|
-
|
|
369
|
-
self.logger.info(
|
|
370
|
-
f"Saved {agent_id} memory to project directory: {memory_file}"
|
|
371
|
-
)
|
|
372
|
-
return True
|
|
373
|
-
except Exception as e:
|
|
374
|
-
self.logger.error(f"Error saving memory for {agent_id}: {e}")
|
|
375
|
-
return False
|
|
376
|
-
|
|
377
227
|
def optimize_memory(self, agent_id: Optional[str] = None) -> Dict[str, Any]:
|
|
378
228
|
"""Optimize agent memory by consolidating/cleaning memories.
|
|
379
229
|
|
|
@@ -596,10 +446,12 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
596
446
|
current_memory = self.load_agent_memory(agent_id)
|
|
597
447
|
|
|
598
448
|
# Parse existing memory into a simple list
|
|
599
|
-
existing_items = self.
|
|
449
|
+
existing_items = self.format_service.parse_memory_list(current_memory)
|
|
600
450
|
|
|
601
451
|
# Clean template placeholders if this is a fresh memory
|
|
602
|
-
existing_items = self.
|
|
452
|
+
existing_items = self.format_service.clean_template_placeholders_list(
|
|
453
|
+
existing_items
|
|
454
|
+
)
|
|
603
455
|
|
|
604
456
|
# Add new learnings, avoiding duplicates
|
|
605
457
|
updated = False
|
|
@@ -638,10 +490,12 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
638
490
|
return True # Not an error, just nothing new to add
|
|
639
491
|
|
|
640
492
|
# Rebuild memory content as simple list with updated timestamp
|
|
641
|
-
new_content = self.
|
|
493
|
+
new_content = self.format_service.build_simple_memory_content(
|
|
494
|
+
agent_id, existing_items
|
|
495
|
+
)
|
|
642
496
|
|
|
643
497
|
# Validate and save
|
|
644
|
-
agent_limits = self.
|
|
498
|
+
agent_limits = self.limits_service.get_agent_limits(agent_id)
|
|
645
499
|
if self.content_manager.exceeds_limits(new_content, agent_limits):
|
|
646
500
|
self.logger.debug(f"Memory for {agent_id} exceeds limits, truncating")
|
|
647
501
|
new_content = self.content_manager.truncate_simple_list(
|
|
@@ -649,251 +503,12 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
649
503
|
)
|
|
650
504
|
|
|
651
505
|
# All memories go to project directory
|
|
652
|
-
return self.
|
|
506
|
+
return self._save_memory_file_wrapper(agent_id, new_content)
|
|
653
507
|
|
|
654
508
|
except Exception as e:
|
|
655
509
|
self.logger.error(f"Error adding learnings to memory for {agent_id}: {e}")
|
|
656
510
|
return False
|
|
657
511
|
|
|
658
|
-
def _parse_memory_list(self, memory_content: str) -> List[str]:
|
|
659
|
-
"""Parse memory content into a simple list.
|
|
660
|
-
|
|
661
|
-
Args:
|
|
662
|
-
memory_content: Raw memory file content
|
|
663
|
-
|
|
664
|
-
Returns:
|
|
665
|
-
List of memory items
|
|
666
|
-
"""
|
|
667
|
-
items = []
|
|
668
|
-
|
|
669
|
-
for line in memory_content.split("\n"):
|
|
670
|
-
line = line.strip()
|
|
671
|
-
# Skip metadata lines and headers
|
|
672
|
-
if line.startswith(("<!-- ", "#")) or not line:
|
|
673
|
-
continue
|
|
674
|
-
# Collect items (with or without bullet points)
|
|
675
|
-
if line.startswith("- "):
|
|
676
|
-
items.append(line)
|
|
677
|
-
elif line and not line.startswith("##"): # Legacy format without bullets
|
|
678
|
-
items.append(f"- {line}")
|
|
679
|
-
|
|
680
|
-
return items
|
|
681
|
-
|
|
682
|
-
def _clean_template_placeholders_list(self, items: List[str]) -> List[str]:
|
|
683
|
-
"""Remove template placeholder text from item list.
|
|
684
|
-
|
|
685
|
-
Args:
|
|
686
|
-
items: List of memory items
|
|
687
|
-
|
|
688
|
-
Returns:
|
|
689
|
-
List with placeholder text removed
|
|
690
|
-
"""
|
|
691
|
-
# Template placeholder patterns to remove
|
|
692
|
-
placeholders = [
|
|
693
|
-
"Analyze project structure to understand architecture patterns",
|
|
694
|
-
"Observe codebase patterns and conventions during tasks",
|
|
695
|
-
"Extract implementation guidelines from project documentation",
|
|
696
|
-
"Learn from errors encountered during project work",
|
|
697
|
-
"Project analysis pending - gather context during tasks",
|
|
698
|
-
"claude-mpm: Software project requiring analysis",
|
|
699
|
-
]
|
|
700
|
-
|
|
701
|
-
cleaned = []
|
|
702
|
-
for item in items:
|
|
703
|
-
# Remove bullet point for comparison
|
|
704
|
-
item_text = item.lstrip("- ").strip()
|
|
705
|
-
# Keep item if it's not a placeholder
|
|
706
|
-
if item_text and item_text not in placeholders:
|
|
707
|
-
cleaned.append(item)
|
|
708
|
-
|
|
709
|
-
return cleaned
|
|
710
|
-
|
|
711
|
-
def _clean_template_placeholders(
|
|
712
|
-
self, sections: Dict[str, List[str]]
|
|
713
|
-
) -> Dict[str, List[str]]:
|
|
714
|
-
"""Remove template placeholder text from sections.
|
|
715
|
-
|
|
716
|
-
Args:
|
|
717
|
-
sections: Dict mapping section names to lists of items
|
|
718
|
-
|
|
719
|
-
Returns:
|
|
720
|
-
Dict with placeholder text removed
|
|
721
|
-
"""
|
|
722
|
-
# Template placeholder patterns to remove
|
|
723
|
-
placeholders = [
|
|
724
|
-
"Analyze project structure to understand architecture patterns",
|
|
725
|
-
"Observe codebase patterns and conventions during tasks",
|
|
726
|
-
"Extract implementation guidelines from project documentation",
|
|
727
|
-
"Learn from errors encountered during project work",
|
|
728
|
-
"Project analysis pending - gather context during tasks",
|
|
729
|
-
"claude-mpm: Software project requiring analysis",
|
|
730
|
-
]
|
|
731
|
-
|
|
732
|
-
cleaned = {}
|
|
733
|
-
for section_name, items in sections.items():
|
|
734
|
-
cleaned_items = []
|
|
735
|
-
for item in items:
|
|
736
|
-
# Remove bullet point for comparison
|
|
737
|
-
item_text = item.lstrip("- ").strip()
|
|
738
|
-
# Keep item if it's not a placeholder
|
|
739
|
-
if item_text and item_text not in placeholders:
|
|
740
|
-
cleaned_items.append(item)
|
|
741
|
-
|
|
742
|
-
# Only include section if it has real content
|
|
743
|
-
if cleaned_items:
|
|
744
|
-
cleaned[section_name] = cleaned_items
|
|
745
|
-
|
|
746
|
-
return cleaned
|
|
747
|
-
|
|
748
|
-
def _categorize_learning(self, learning: str) -> str:
|
|
749
|
-
"""Categorize a learning item into appropriate section.
|
|
750
|
-
|
|
751
|
-
Args:
|
|
752
|
-
learning: The learning string to categorize
|
|
753
|
-
|
|
754
|
-
Returns:
|
|
755
|
-
str: The section name for this learning
|
|
756
|
-
"""
|
|
757
|
-
learning_lower = learning.lower()
|
|
758
|
-
|
|
759
|
-
# Check for keywords to categorize with improved patterns
|
|
760
|
-
# Order matters - more specific patterns should come first
|
|
761
|
-
|
|
762
|
-
# Architecture keywords
|
|
763
|
-
if any(
|
|
764
|
-
word in learning_lower
|
|
765
|
-
for word in [
|
|
766
|
-
"architecture",
|
|
767
|
-
"structure",
|
|
768
|
-
"design",
|
|
769
|
-
"module",
|
|
770
|
-
"component",
|
|
771
|
-
"microservices",
|
|
772
|
-
"service-oriented",
|
|
773
|
-
]
|
|
774
|
-
):
|
|
775
|
-
return "Project Architecture"
|
|
776
|
-
|
|
777
|
-
# Integration keywords (check before patterns to avoid "use" conflict)
|
|
778
|
-
if any(
|
|
779
|
-
word in learning_lower
|
|
780
|
-
for word in [
|
|
781
|
-
"integration",
|
|
782
|
-
"interface",
|
|
783
|
-
"api",
|
|
784
|
-
"connection",
|
|
785
|
-
"database",
|
|
786
|
-
"pooling",
|
|
787
|
-
"via",
|
|
788
|
-
]
|
|
789
|
-
):
|
|
790
|
-
return "Integration Points"
|
|
791
|
-
|
|
792
|
-
# Mistake keywords (check before patterns to avoid conflicts)
|
|
793
|
-
if any(
|
|
794
|
-
word in learning_lower
|
|
795
|
-
for word in ["mistake", "error", "avoid", "don't", "never", "not"]
|
|
796
|
-
):
|
|
797
|
-
return "Common Mistakes to Avoid"
|
|
798
|
-
|
|
799
|
-
# Context keywords (check before patterns to avoid "working", "version" conflicts)
|
|
800
|
-
if any(
|
|
801
|
-
word in learning_lower
|
|
802
|
-
for word in [
|
|
803
|
-
"context",
|
|
804
|
-
"current",
|
|
805
|
-
"currently",
|
|
806
|
-
"working",
|
|
807
|
-
"version",
|
|
808
|
-
"release",
|
|
809
|
-
"candidate",
|
|
810
|
-
]
|
|
811
|
-
):
|
|
812
|
-
return "Current Technical Context"
|
|
813
|
-
|
|
814
|
-
# Guideline keywords (check before patterns to avoid "must", "should" conflicts)
|
|
815
|
-
if any(
|
|
816
|
-
word in learning_lower
|
|
817
|
-
for word in [
|
|
818
|
-
"guideline",
|
|
819
|
-
"rule",
|
|
820
|
-
"standard",
|
|
821
|
-
"practice",
|
|
822
|
-
"docstring",
|
|
823
|
-
"documentation",
|
|
824
|
-
"must",
|
|
825
|
-
"should",
|
|
826
|
-
"include",
|
|
827
|
-
"comprehensive",
|
|
828
|
-
]
|
|
829
|
-
):
|
|
830
|
-
return "Implementation Guidelines"
|
|
831
|
-
|
|
832
|
-
# Pattern keywords (including dependency injection, conventions)
|
|
833
|
-
if any(
|
|
834
|
-
word in learning_lower
|
|
835
|
-
for word in [
|
|
836
|
-
"pattern",
|
|
837
|
-
"convention",
|
|
838
|
-
"style",
|
|
839
|
-
"format",
|
|
840
|
-
"dependency injection",
|
|
841
|
-
"instantiation",
|
|
842
|
-
"use",
|
|
843
|
-
"implement",
|
|
844
|
-
]
|
|
845
|
-
):
|
|
846
|
-
return "Coding Patterns Learned"
|
|
847
|
-
|
|
848
|
-
# Strategy keywords
|
|
849
|
-
if any(
|
|
850
|
-
word in learning_lower
|
|
851
|
-
for word in ["strategy", "approach", "method", "technique", "effective"]
|
|
852
|
-
):
|
|
853
|
-
return "Effective Strategies"
|
|
854
|
-
|
|
855
|
-
# Performance keywords
|
|
856
|
-
if any(
|
|
857
|
-
word in learning_lower
|
|
858
|
-
for word in ["performance", "optimization", "speed", "efficiency"]
|
|
859
|
-
):
|
|
860
|
-
return "Performance Considerations"
|
|
861
|
-
|
|
862
|
-
# Domain keywords
|
|
863
|
-
if any(word in learning_lower for word in ["domain", "business", "specific"]):
|
|
864
|
-
return "Domain-Specific Knowledge"
|
|
865
|
-
|
|
866
|
-
return "Recent Learnings"
|
|
867
|
-
|
|
868
|
-
def _build_simple_memory_content(self, agent_id: str, items: List[str]) -> str:
|
|
869
|
-
"""Build memory content as a simple list with updated timestamp.
|
|
870
|
-
|
|
871
|
-
Args:
|
|
872
|
-
agent_id: The agent identifier
|
|
873
|
-
items: List of memory items
|
|
874
|
-
|
|
875
|
-
Returns:
|
|
876
|
-
str: The formatted memory content
|
|
877
|
-
"""
|
|
878
|
-
lines = []
|
|
879
|
-
|
|
880
|
-
# Add header
|
|
881
|
-
lines.append(f"# Agent Memory: {agent_id}")
|
|
882
|
-
# Always update timestamp when building new content
|
|
883
|
-
lines.append(f"<!-- Last Updated: {datetime.now().isoformat()}Z -->")
|
|
884
|
-
lines.append("")
|
|
885
|
-
|
|
886
|
-
# Add all items as a simple list
|
|
887
|
-
for item in items:
|
|
888
|
-
if item.strip():
|
|
889
|
-
# Ensure item has bullet point
|
|
890
|
-
if not item.strip().startswith("-"):
|
|
891
|
-
lines.append(f"- {item.strip()}")
|
|
892
|
-
else:
|
|
893
|
-
lines.append(item.strip())
|
|
894
|
-
|
|
895
|
-
return "\n".join(lines)
|
|
896
|
-
|
|
897
512
|
def replace_agent_memory(self, agent_id: str, memory_items: List[str]) -> bool:
|
|
898
513
|
"""Replace agent's memory with new content as a simple list.
|
|
899
514
|
|
|
@@ -910,10 +525,12 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
910
525
|
"""
|
|
911
526
|
try:
|
|
912
527
|
# Build new memory content as simple list with updated timestamp
|
|
913
|
-
new_content = self.
|
|
528
|
+
new_content = self.format_service.build_simple_memory_content(
|
|
529
|
+
agent_id, memory_items
|
|
530
|
+
)
|
|
914
531
|
|
|
915
532
|
# Validate and save
|
|
916
|
-
agent_limits = self.
|
|
533
|
+
agent_limits = self.limits_service.get_agent_limits(agent_id)
|
|
917
534
|
if self.content_manager.exceeds_limits(new_content, agent_limits):
|
|
918
535
|
self.logger.debug(f"Memory for {agent_id} exceeds limits, truncating")
|
|
919
536
|
new_content = self.content_manager.truncate_simple_list(
|
|
@@ -921,7 +538,7 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
921
538
|
)
|
|
922
539
|
|
|
923
540
|
# Save the new memory
|
|
924
|
-
return self.
|
|
541
|
+
return self._save_memory_file_wrapper(agent_id, new_content)
|
|
925
542
|
|
|
926
543
|
except Exception as e:
|
|
927
544
|
self.logger.error(f"Error replacing memory for {agent_id}: {e}")
|
|
@@ -1001,105 +618,20 @@ class AgentMemoryManager(MemoryServiceInterface):
|
|
|
1001
618
|
"suggestion": "Use load_agent_memory() for specific agent memories",
|
|
1002
619
|
}
|
|
1003
620
|
|
|
1004
|
-
def
|
|
1005
|
-
"""
|
|
1006
|
-
|
|
1007
|
-
WHY: The memories directory needs clear documentation so developers
|
|
1008
|
-
understand the purpose of these files and how to interact with them.
|
|
1009
|
-
"""
|
|
1010
|
-
try:
|
|
1011
|
-
self.memories_dir.mkdir(parents=True, exist_ok=True)
|
|
1012
|
-
self.logger.debug(f"Ensured memories directory exists: {self.memories_dir}")
|
|
1013
|
-
|
|
1014
|
-
readme_path = self.memories_dir / "README.md"
|
|
1015
|
-
if not readme_path.exists():
|
|
1016
|
-
readme_content = """# Agent Memory System
|
|
1017
|
-
|
|
1018
|
-
## Purpose
|
|
1019
|
-
Each agent maintains project-specific knowledge in these files. Agents read their memory file before tasks and update it when they learn something new.
|
|
1020
|
-
|
|
1021
|
-
## Manual Editing
|
|
1022
|
-
Feel free to edit these files to:
|
|
1023
|
-
- Add project-specific guidelines
|
|
1024
|
-
- Remove outdated information
|
|
1025
|
-
- Reorganize for better clarity
|
|
1026
|
-
- Add domain-specific knowledge
|
|
1027
|
-
|
|
1028
|
-
## Memory Limits
|
|
1029
|
-
- Max file size: 80KB (~20k tokens)
|
|
1030
|
-
- Max sections: 10
|
|
1031
|
-
- Max items per section: 15
|
|
1032
|
-
- Files auto-truncate when limits exceeded
|
|
1033
|
-
|
|
1034
|
-
## File Format
|
|
1035
|
-
Standard markdown with structured sections. Agents expect:
|
|
1036
|
-
- Project Architecture
|
|
1037
|
-
- Implementation Guidelines
|
|
1038
|
-
- Common Mistakes to Avoid
|
|
1039
|
-
- Current Technical Context
|
|
1040
|
-
|
|
1041
|
-
## How It Works
|
|
1042
|
-
1. Agents read their memory file before starting tasks
|
|
1043
|
-
2. Agents add learnings during or after task completion
|
|
1044
|
-
3. Files automatically enforce size limits
|
|
1045
|
-
4. Developers can manually edit for accuracy
|
|
1046
|
-
|
|
1047
|
-
## Memory File Lifecycle
|
|
1048
|
-
- Created automatically when agent first runs
|
|
1049
|
-
- Updated through hook system after delegations
|
|
1050
|
-
- Manually editable by developers
|
|
1051
|
-
- Version controlled with project
|
|
1052
|
-
"""
|
|
1053
|
-
readme_path.write_text(readme_content, encoding="utf-8")
|
|
1054
|
-
self.logger.info("Created README.md in memories directory")
|
|
1055
|
-
|
|
1056
|
-
except Exception as e:
|
|
1057
|
-
self.logger.error(f"Error ensuring memories directory: {e}")
|
|
1058
|
-
# Continue anyway - memory system should not block operations
|
|
1059
|
-
|
|
1060
|
-
def _parse_memory_sections(self, memory_content: str) -> Dict[str, List[str]]:
|
|
1061
|
-
"""Parse memory content into sections and items.
|
|
621
|
+
def _save_memory_file_wrapper(self, agent_id: str, content: str) -> bool:
|
|
622
|
+
"""Wrapper for save_memory_file that handles agent_id.
|
|
1062
623
|
|
|
1063
624
|
Args:
|
|
1064
|
-
|
|
625
|
+
agent_id: Agent identifier
|
|
626
|
+
content: Content to save
|
|
1065
627
|
|
|
1066
628
|
Returns:
|
|
1067
|
-
|
|
629
|
+
True if saved successfully
|
|
1068
630
|
"""
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
for line in memory_content.split("\n"):
|
|
1074
|
-
# Skip metadata lines
|
|
1075
|
-
if line.startswith("<!-- ") and line.endswith(" -->"):
|
|
1076
|
-
continue
|
|
1077
|
-
# Check for section headers (## Level 2 headers)
|
|
1078
|
-
if line.startswith("## "):
|
|
1079
|
-
# Save previous section if exists
|
|
1080
|
-
if current_section and current_items:
|
|
1081
|
-
sections[current_section] = current_items
|
|
1082
|
-
|
|
1083
|
-
# Start new section
|
|
1084
|
-
current_section = line[3:].strip() # Remove "## " prefix
|
|
1085
|
-
current_items = []
|
|
1086
|
-
# Collect non-empty lines as items (but not HTML comments)
|
|
1087
|
-
elif (
|
|
1088
|
-
line.strip() and current_section and not line.strip().startswith("<!--")
|
|
1089
|
-
):
|
|
1090
|
-
# Keep the full line with its formatting
|
|
1091
|
-
current_items.append(line.strip())
|
|
1092
|
-
|
|
1093
|
-
# Save last section
|
|
1094
|
-
if current_section and current_items:
|
|
1095
|
-
sections[current_section] = current_items
|
|
1096
|
-
|
|
1097
|
-
return sections
|
|
1098
|
-
|
|
1099
|
-
# ================================================================================
|
|
1100
|
-
# Interface Adapter Methods
|
|
1101
|
-
# ================================================================================
|
|
1102
|
-
# These methods adapt the existing implementation to comply with MemoryServiceInterface
|
|
631
|
+
file_path = self.file_service.get_memory_file_with_migration(
|
|
632
|
+
self.memories_dir, agent_id
|
|
633
|
+
)
|
|
634
|
+
return self.file_service.save_memory_file(file_path, content)
|
|
1103
635
|
|
|
1104
636
|
def load_memory(self, agent_id: str) -> Optional[str]:
|
|
1105
637
|
"""Load memory for a specific agent.
|
|
@@ -1189,13 +721,15 @@ Standard markdown with structured sections. Agents expect:
|
|
|
1189
721
|
size_kb = memory_file.stat().st_size / 1024
|
|
1190
722
|
metrics["agents"][agent_id] = {
|
|
1191
723
|
"size_kb": round(size_kb, 2),
|
|
1192
|
-
"limit_kb": self.
|
|
724
|
+
"limit_kb": self.limits_service.get_agent_limits(agent_id)[
|
|
1193
725
|
"max_file_size_kb"
|
|
1194
726
|
],
|
|
1195
727
|
"usage_percent": round(
|
|
1196
728
|
(
|
|
1197
729
|
size_kb
|
|
1198
|
-
/ self.
|
|
730
|
+
/ self.limits_service.get_agent_limits(agent_id)[
|
|
731
|
+
"max_file_size_kb"
|
|
732
|
+
]
|
|
1199
733
|
)
|
|
1200
734
|
* 100,
|
|
1201
735
|
1,
|
|
@@ -1210,7 +744,7 @@ Standard markdown with structured sections. Agents expect:
|
|
|
1210
744
|
if file_path.name != "README.md":
|
|
1211
745
|
agent_name = file_path.stem.replace("_memories", "")
|
|
1212
746
|
size_kb = file_path.stat().st_size / 1024
|
|
1213
|
-
limit_kb = self.
|
|
747
|
+
limit_kb = self.limits_service.get_agent_limits(agent_name)[
|
|
1214
748
|
"max_file_size_kb"
|
|
1215
749
|
]
|
|
1216
750
|
metrics["agents"][agent_name] = {
|