claude-mpm 4.1.2__py3-none-any.whl → 4.1.3__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 (53) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/templates/engineer.json +33 -11
  3. claude_mpm/cli/commands/agents.py +556 -1009
  4. claude_mpm/cli/commands/memory.py +248 -927
  5. claude_mpm/cli/commands/run.py +139 -484
  6. claude_mpm/cli/startup_logging.py +76 -0
  7. claude_mpm/core/agent_registry.py +6 -10
  8. claude_mpm/core/framework_loader.py +114 -595
  9. claude_mpm/core/logging_config.py +2 -4
  10. claude_mpm/hooks/claude_hooks/event_handlers.py +7 -117
  11. claude_mpm/hooks/claude_hooks/hook_handler.py +91 -755
  12. claude_mpm/hooks/claude_hooks/hook_handler_original.py +1040 -0
  13. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +347 -0
  14. claude_mpm/hooks/claude_hooks/services/__init__.py +13 -0
  15. claude_mpm/hooks/claude_hooks/services/connection_manager.py +190 -0
  16. claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
  17. claude_mpm/hooks/claude_hooks/services/state_manager.py +282 -0
  18. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
  19. claude_mpm/services/agents/deployment/agent_deployment.py +42 -454
  20. claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
  21. claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
  22. claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
  23. claude_mpm/services/agents/memory/agent_memory_manager.py +42 -508
  24. claude_mpm/services/agents/memory/memory_categorization_service.py +165 -0
  25. claude_mpm/services/agents/memory/memory_file_service.py +103 -0
  26. claude_mpm/services/agents/memory/memory_format_service.py +201 -0
  27. claude_mpm/services/agents/memory/memory_limits_service.py +99 -0
  28. claude_mpm/services/agents/registry/__init__.py +1 -1
  29. claude_mpm/services/cli/__init__.py +18 -0
  30. claude_mpm/services/cli/agent_cleanup_service.py +407 -0
  31. claude_mpm/services/cli/agent_dependency_service.py +395 -0
  32. claude_mpm/services/cli/agent_listing_service.py +463 -0
  33. claude_mpm/services/cli/agent_output_formatter.py +605 -0
  34. claude_mpm/services/cli/agent_validation_service.py +589 -0
  35. claude_mpm/services/cli/dashboard_launcher.py +424 -0
  36. claude_mpm/services/cli/memory_crud_service.py +617 -0
  37. claude_mpm/services/cli/memory_output_formatter.py +604 -0
  38. claude_mpm/services/cli/session_manager.py +513 -0
  39. claude_mpm/services/cli/socketio_manager.py +498 -0
  40. claude_mpm/services/cli/startup_checker.py +370 -0
  41. claude_mpm/services/core/cache_manager.py +311 -0
  42. claude_mpm/services/core/memory_manager.py +637 -0
  43. claude_mpm/services/core/path_resolver.py +498 -0
  44. claude_mpm/services/core/service_container.py +520 -0
  45. claude_mpm/services/core/service_interfaces.py +436 -0
  46. claude_mpm/services/diagnostics/checks/agent_check.py +65 -19
  47. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/METADATA +1 -1
  48. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/RECORD +52 -22
  49. claude_mpm/cli/commands/run_config_checker.py +0 -159
  50. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/WHEEL +0 -0
  51. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/entry_points.txt +0 -0
  52. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/licenses/LICENSE +0 -0
  53. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.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
- # Ensure project directory exists
87
- self._ensure_memories_directory()
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
- # Initialize memory limits from configuration
90
- self._init_memory_limits()
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._get_memory_file_with_migration(
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._get_agent_limits(agent_id)
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._parse_memory_list(current_memory)
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._clean_template_placeholders_list(existing_items)
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._build_simple_memory_content(agent_id, existing_items)
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._get_agent_limits(agent_id)
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._save_memory_file(agent_id, new_content)
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._build_simple_memory_content(agent_id, memory_items)
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._get_agent_limits(agent_id)
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._save_memory_file(agent_id, new_content)
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 _ensure_memories_directory(self):
1005
- """Ensure memories directory exists with README.
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
- memory_content: Raw memory file content
625
+ agent_id: Agent identifier
626
+ content: Content to save
1065
627
 
1066
628
  Returns:
1067
- Dict mapping section names to lists of items
629
+ True if saved successfully
1068
630
  """
1069
- sections = {}
1070
- current_section = None
1071
- current_items = []
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._get_agent_limits(agent_id)[
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._get_agent_limits(agent_id)["max_file_size_kb"]
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._get_agent_limits(agent_name)[
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] = {