claude-mpm 3.1.1__py3-none-any.whl → 3.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 (47) hide show
  1. claude_mpm/__main__.py +27 -3
  2. claude_mpm/agents/INSTRUCTIONS.md +17 -2
  3. claude_mpm/agents/templates/test-integration-agent.md +34 -0
  4. claude_mpm/cli/README.md +109 -0
  5. claude_mpm/cli/__init__.py +172 -0
  6. claude_mpm/cli/commands/__init__.py +20 -0
  7. claude_mpm/cli/commands/agents.py +202 -0
  8. claude_mpm/cli/commands/info.py +94 -0
  9. claude_mpm/cli/commands/run.py +95 -0
  10. claude_mpm/cli/commands/tickets.py +70 -0
  11. claude_mpm/cli/commands/ui.py +79 -0
  12. claude_mpm/cli/parser.py +337 -0
  13. claude_mpm/cli/utils.py +190 -0
  14. claude_mpm/cli_enhancements.py +19 -0
  15. claude_mpm/core/agent_registry.py +4 -4
  16. claude_mpm/core/factories.py +1 -1
  17. claude_mpm/core/service_registry.py +1 -1
  18. claude_mpm/core/simple_runner.py +17 -27
  19. claude_mpm/hooks/claude_hooks/hook_handler.py +53 -4
  20. claude_mpm/models/__init__.py +106 -0
  21. claude_mpm/models/agent_definition.py +196 -0
  22. claude_mpm/models/common.py +41 -0
  23. claude_mpm/models/lifecycle.py +97 -0
  24. claude_mpm/models/modification.py +126 -0
  25. claude_mpm/models/persistence.py +57 -0
  26. claude_mpm/models/registry.py +91 -0
  27. claude_mpm/security/__init__.py +8 -0
  28. claude_mpm/security/bash_validator.py +393 -0
  29. claude_mpm/services/agent_lifecycle_manager.py +206 -94
  30. claude_mpm/services/agent_modification_tracker.py +27 -100
  31. claude_mpm/services/agent_persistence_service.py +74 -0
  32. claude_mpm/services/agent_registry.py +43 -82
  33. claude_mpm/services/agent_versioning.py +37 -0
  34. claude_mpm/services/{ticketing_service_original.py → legacy_ticketing_service.py} +16 -9
  35. claude_mpm/services/ticket_manager.py +5 -4
  36. claude_mpm/services/{ticket_manager_di.py → ticket_manager_dependency_injection.py} +39 -12
  37. claude_mpm/services/version_control/semantic_versioning.py +10 -9
  38. claude_mpm/utils/path_operations.py +20 -0
  39. {claude_mpm-3.1.1.dist-info → claude_mpm-3.1.3.dist-info}/METADATA +9 -1
  40. {claude_mpm-3.1.1.dist-info → claude_mpm-3.1.3.dist-info}/RECORD +45 -25
  41. claude_mpm/cli_main.py +0 -13
  42. claude_mpm/utils/import_migration_example.py +0 -80
  43. /claude_mpm/{cli.py → cli_old.py} +0 -0
  44. {claude_mpm-3.1.1.dist-info → claude_mpm-3.1.3.dist-info}/WHEEL +0 -0
  45. {claude_mpm-3.1.1.dist-info → claude_mpm-3.1.3.dist-info}/entry_points.txt +0 -0
  46. {claude_mpm-3.1.1.dist-info → claude_mpm-3.1.3.dist-info}/licenses/LICENSE +0 -0
  47. {claude_mpm-3.1.1.dist-info → claude_mpm-3.1.3.dist-info}/top_level.txt +0 -0
@@ -27,95 +27,46 @@ Created for ISS-0118: Agent Registry and Hierarchical Discovery System
27
27
  import asyncio
28
28
  import logging
29
29
  import time
30
- from dataclasses import dataclass, field
31
30
  from datetime import datetime, timedelta
32
- from enum import Enum
33
31
  from pathlib import Path
34
32
  from typing import Dict, List, Optional, Set, Any, Tuple, Union
35
33
 
36
34
  from claude_mpm.services.shared_prompt_cache import SharedPromptCache
37
- from claude_mpm.services.agent_registry import AgentRegistry, AgentMetadata
38
- from claude_mpm.services.agent_modification_tracker import (
39
- AgentModificationTracker,
40
- AgentModification,
41
- ModificationType,
35
+ from claude_mpm.services.agent_registry import AgentRegistry
36
+ from claude_mpm.services.agent_modification_tracker import AgentModificationTracker
37
+ from claude_mpm.services.agent_persistence_service import AgentPersistenceService
38
+ from claude_mpm.services.agent_management_service import AgentManager
39
+ from claude_mpm.models.agent_definition import AgentDefinition, AgentType
40
+ from claude_mpm.models.lifecycle import (
41
+ LifecycleOperation,
42
+ LifecycleState,
43
+ AgentLifecycleRecord,
44
+ LifecycleOperationResult
45
+ )
46
+ from claude_mpm.models.modification import (
47
+ AgentModification,
48
+ ModificationType,
42
49
  ModificationTier
43
50
  )
44
- from claude_mpm.services.agent_persistence_service import (
45
- AgentPersistenceService,
46
- PersistenceRecord,
51
+ from claude_mpm.models.persistence import (
47
52
  PersistenceStrategy,
53
+ PersistenceRecord,
48
54
  PersistenceOperation
49
55
  )
56
+ from claude_mpm.models.registry import AgentRegistryMetadata
50
57
  from claude_mpm.core.base_service import BaseService
51
58
  from claude_mpm.utils.path_operations import path_ops
52
59
  from claude_mpm.utils.config_manager import ConfigurationManager
53
60
 
54
-
55
- class LifecycleOperation(Enum):
56
- """Agent lifecycle operations."""
57
- CREATE = "create"
58
- UPDATE = "update"
59
- DELETE = "delete"
60
- RESTORE = "restore"
61
- MIGRATE = "migrate"
62
- REPLICATE = "replicate"
63
- VALIDATE = "validate"
64
-
65
-
66
- class LifecycleState(Enum):
67
- """Agent lifecycle states."""
68
- ACTIVE = "active"
69
- MODIFIED = "modified"
70
- DELETED = "deleted"
71
- CONFLICTED = "conflicted"
72
- MIGRATING = "migrating"
73
- VALIDATING = "validating"
74
-
75
-
76
- @dataclass
77
- class AgentLifecycleRecord:
78
- """Complete lifecycle record for an agent."""
79
-
80
- agent_name: str
81
- current_state: LifecycleState
82
- tier: ModificationTier
83
- file_path: str
84
- created_at: float
85
- last_modified: float
86
- version: str
87
- modifications: List[str] = field(default_factory=list) # Modification IDs
88
- persistence_operations: List[str] = field(default_factory=list) # Operation IDs
89
- backup_paths: List[str] = field(default_factory=list)
90
- validation_status: str = "valid"
91
- validation_errors: List[str] = field(default_factory=list)
92
- metadata: Dict[str, Any] = field(default_factory=dict)
93
-
94
- @property
95
- def age_days(self) -> float:
96
- """Get age in days."""
97
- return (time.time() - self.created_at) / (24 * 3600)
98
-
99
- @property
100
- def last_modified_datetime(self) -> datetime:
101
- """Get last modified as datetime."""
102
- return datetime.fromtimestamp(self.last_modified)
103
-
104
-
105
- @dataclass
106
- class LifecycleOperationResult:
107
- """Result of a lifecycle operation."""
108
-
109
- operation: LifecycleOperation
110
- agent_name: str
111
- success: bool
112
- duration_ms: float
113
- error_message: Optional[str] = None
114
- modification_id: Optional[str] = None
115
- persistence_id: Optional[str] = None
116
- cache_invalidated: bool = False
117
- registry_updated: bool = False
118
- metadata: Dict[str, Any] = field(default_factory=dict)
61
+ # Backward compatibility imports
62
+ # These allow existing code to import from this module
63
+ __all__ = [
64
+ 'LifecycleOperation',
65
+ 'LifecycleState',
66
+ 'AgentLifecycleRecord',
67
+ 'LifecycleOperationResult',
68
+ 'AgentLifecycleManager'
69
+ ]
119
70
 
120
71
 
121
72
  class AgentLifecycleManager(BaseService):
@@ -149,6 +100,7 @@ class AgentLifecycleManager(BaseService):
149
100
  self.agent_registry: Optional[AgentRegistry] = None
150
101
  self.modification_tracker: Optional[AgentModificationTracker] = None
151
102
  self.persistence_service: Optional[AgentPersistenceService] = None
103
+ self.agent_manager: Optional[AgentManager] = None
152
104
 
153
105
  # Lifecycle tracking
154
106
  self.agent_records: Dict[str, AgentLifecycleRecord] = {}
@@ -246,6 +198,9 @@ class AgentLifecycleManager(BaseService):
246
198
  self.persistence_service = AgentPersistenceService()
247
199
  await self.persistence_service.start()
248
200
 
201
+ # Initialize AgentManager
202
+ self.agent_manager = AgentManager()
203
+
249
204
  self.logger.info("Core services initialized successfully")
250
205
 
251
206
  except Exception as e:
@@ -319,9 +274,9 @@ class AgentLifecycleManager(BaseService):
319
274
  if not self.agent_registry:
320
275
  return
321
276
 
322
- # Discover all agents via registry
323
- await self.agent_registry.discover_agents()
324
- all_agents = await self.agent_registry.list_agents()
277
+ # Discover all agents via registry (sync methods)
278
+ self.agent_registry.discover_agents()
279
+ all_agents = self.agent_registry.list_agents()
325
280
 
326
281
  # Update lifecycle records with registry data
327
282
  for agent_metadata in all_agents:
@@ -391,8 +346,32 @@ class AgentLifecycleManager(BaseService):
391
346
  error_message="Agent already exists"
392
347
  )
393
348
 
394
- # Determine file path
395
- file_path = await self._determine_agent_file_path(agent_name, tier)
349
+ # Create agent definition
350
+ agent_def = await self._create_agent_definition(
351
+ agent_name, agent_content, tier, agent_type, **kwargs
352
+ )
353
+
354
+ # Determine location based on tier
355
+ location = "project" if tier == ModificationTier.PROJECT else "framework"
356
+
357
+ # Create agent using AgentManager (sync call in executor)
358
+ try:
359
+ if self.agent_manager:
360
+ file_path = await self._run_sync_in_executor(
361
+ self.agent_manager.create_agent,
362
+ agent_name, agent_def, location
363
+ )
364
+ else:
365
+ # Fallback to direct file creation if AgentManager not available
366
+ file_path = await self._determine_agent_file_path(agent_name, tier)
367
+ path_ops.ensure_dir(file_path.parent)
368
+ path_ops.safe_write(file_path, agent_content)
369
+ except Exception as e:
370
+ self.logger.error(f"AgentManager failed to create agent: {e}")
371
+ # Fallback to direct file creation
372
+ file_path = await self._determine_agent_file_path(agent_name, tier)
373
+ path_ops.ensure_dir(file_path.parent)
374
+ path_ops.safe_write(file_path, agent_content)
396
375
 
397
376
  # Track modification
398
377
  modification = await self.modification_tracker.track_modification(
@@ -404,13 +383,18 @@ class AgentLifecycleManager(BaseService):
404
383
  **kwargs
405
384
  )
406
385
 
407
- # Persist agent
408
- persistence_record = await self.persistence_service.persist_agent(
386
+ # Note: We don't use persistence_service for the actual write anymore
387
+ # since AgentManager handles that. We create a synthetic record for compatibility.
388
+ persistence_record = PersistenceRecord(
389
+ operation_id=f"create_{agent_name}_{time.time()}",
390
+ operation_type=PersistenceOperation.CREATE,
409
391
  agent_name=agent_name,
410
- agent_content=agent_content,
411
392
  source_tier=tier,
412
393
  target_tier=tier,
413
- strategy=self.default_persistence_strategy
394
+ strategy=self.default_persistence_strategy,
395
+ success=True,
396
+ timestamp=time.time(),
397
+ file_path=str(file_path)
414
398
  )
415
399
 
416
400
  # Create lifecycle record
@@ -512,6 +496,48 @@ class AgentLifecycleManager(BaseService):
512
496
 
513
497
  record = self.agent_records[agent_name]
514
498
 
499
+ # Update agent using AgentManager
500
+ try:
501
+ if self.agent_manager:
502
+ # Read current agent to get full definition
503
+ current_def = await self._run_sync_in_executor(
504
+ self.agent_manager.read_agent, agent_name
505
+ )
506
+
507
+ if current_def:
508
+ # Update raw content
509
+ current_def.raw_content = agent_content
510
+
511
+ # Apply any metadata updates from kwargs
512
+ if 'model_preference' in kwargs:
513
+ current_def.metadata.model_preference = kwargs['model_preference']
514
+ if 'tags' in kwargs:
515
+ current_def.metadata.tags = kwargs['tags']
516
+ if 'specializations' in kwargs:
517
+ current_def.metadata.specializations = kwargs['specializations']
518
+
519
+ # Update via AgentManager
520
+ updated_def = await self._run_sync_in_executor(
521
+ self.agent_manager.update_agent,
522
+ agent_name, {"raw_content": agent_content}, True
523
+ )
524
+
525
+ if not updated_def:
526
+ raise Exception("AgentManager update failed")
527
+ else:
528
+ raise Exception("Could not read current agent definition")
529
+ else:
530
+ # Fallback to direct file update
531
+ file_path = Path(record.file_path)
532
+ if path_ops.validate_exists(file_path):
533
+ path_ops.safe_write(file_path, agent_content)
534
+ except Exception as e:
535
+ self.logger.error(f"AgentManager failed to update agent: {e}")
536
+ # Fallback to direct file update
537
+ file_path = Path(record.file_path)
538
+ if path_ops.validate_exists(file_path):
539
+ path_ops.safe_write(file_path, agent_content)
540
+
515
541
  # Track modification
516
542
  modification = await self.modification_tracker.track_modification(
517
543
  agent_name=agent_name,
@@ -521,12 +547,17 @@ class AgentLifecycleManager(BaseService):
521
547
  **kwargs
522
548
  )
523
549
 
524
- # Persist agent
525
- persistence_record = await self.persistence_service.persist_agent(
550
+ # Create synthetic persistence record for compatibility
551
+ persistence_record = PersistenceRecord(
552
+ operation_id=f"update_{agent_name}_{time.time()}",
553
+ operation_type=PersistenceOperation.UPDATE,
526
554
  agent_name=agent_name,
527
- agent_content=agent_content,
528
555
  source_tier=record.tier,
529
- strategy=self.default_persistence_strategy
556
+ target_tier=record.tier,
557
+ strategy=self.default_persistence_strategy,
558
+ success=True,
559
+ timestamp=time.time(),
560
+ file_path=record.file_path
530
561
  )
531
562
 
532
563
  # Update lifecycle record
@@ -631,10 +662,28 @@ class AgentLifecycleManager(BaseService):
631
662
  **kwargs
632
663
  )
633
664
 
634
- # Delete file
635
- file_path = Path(record.file_path)
636
- if path_ops.validate_exists(file_path):
637
- path_ops.safe_delete(file_path)
665
+ # Delete agent using AgentManager
666
+ deletion_success = False
667
+ try:
668
+ if self.agent_manager:
669
+ deletion_success = await self._run_sync_in_executor(
670
+ self.agent_manager.delete_agent, agent_name
671
+ )
672
+ if not deletion_success:
673
+ raise Exception("AgentManager delete failed")
674
+ else:
675
+ # Fallback to direct file deletion
676
+ file_path = Path(record.file_path)
677
+ if path_ops.validate_exists(file_path):
678
+ path_ops.safe_delete(file_path)
679
+ deletion_success = True
680
+ except Exception as e:
681
+ self.logger.error(f"AgentManager failed to delete agent: {e}")
682
+ # Fallback to direct file deletion
683
+ file_path = Path(record.file_path)
684
+ if path_ops.validate_exists(file_path):
685
+ path_ops.safe_delete(file_path)
686
+ deletion_success = True
638
687
 
639
688
  # Update lifecycle record
640
689
  record.current_state = LifecycleState.DELETED
@@ -749,8 +798,8 @@ class AgentLifecycleManager(BaseService):
749
798
  return False
750
799
 
751
800
  try:
752
- # Refresh specific agent in registry
753
- await self.agent_registry.refresh_agent(agent_name)
801
+ # Refresh registry (discover_agents is synchronous)
802
+ self.agent_registry.discover_agents()
754
803
  return True
755
804
 
756
805
  except Exception as e:
@@ -950,6 +999,69 @@ class AgentLifecycleManager(BaseService):
950
999
 
951
1000
  return stats
952
1001
 
1002
+ async def _create_agent_definition(self, agent_name: str, agent_content: str,
1003
+ tier: ModificationTier, agent_type: str, **kwargs) -> AgentDefinition:
1004
+ """
1005
+ Create an AgentDefinition from lifecycle parameters.
1006
+
1007
+ WHY: This method bridges the gap between the lifecycle manager's parameters
1008
+ and the AgentManager's expected AgentDefinition model.
1009
+
1010
+ DESIGN DECISION: Creating a minimal AgentDefinition here because:
1011
+ - The full markdown parsing happens in AgentManager
1012
+ - We only need to provide the essential metadata
1013
+ - This keeps the lifecycle manager focused on orchestration
1014
+ """
1015
+ # Map tier to AgentType
1016
+ type_map = {
1017
+ ModificationTier.USER: AgentType.CUSTOM,
1018
+ ModificationTier.PROJECT: AgentType.PROJECT,
1019
+ ModificationTier.SYSTEM: AgentType.SYSTEM
1020
+ }
1021
+
1022
+ # Create metadata
1023
+ from claude_mpm.models.agent_definition import AgentMetadata, AgentPermissions
1024
+ metadata = AgentMetadata(
1025
+ type=type_map.get(tier, AgentType.CUSTOM),
1026
+ model_preference=kwargs.get('model_preference', 'claude-3-sonnet'),
1027
+ version="1.0.0",
1028
+ author=kwargs.get('author', 'claude-mpm'),
1029
+ tags=kwargs.get('tags', []),
1030
+ specializations=kwargs.get('specializations', [])
1031
+ )
1032
+
1033
+ # Create minimal definition
1034
+ definition = AgentDefinition(
1035
+ name=agent_name,
1036
+ title=agent_name.replace('-', ' ').title(),
1037
+ file_path="", # Will be set by AgentManager
1038
+ metadata=metadata,
1039
+ primary_role=kwargs.get('primary_role', f"{agent_name} agent"),
1040
+ when_to_use={"select": [], "do_not_select": []},
1041
+ capabilities=[],
1042
+ authority=AgentPermissions(),
1043
+ workflows=[],
1044
+ escalation_triggers=[],
1045
+ kpis=[],
1046
+ dependencies=[],
1047
+ tools_commands="",
1048
+ raw_content=agent_content
1049
+ )
1050
+
1051
+ return definition
1052
+
1053
+ async def _run_sync_in_executor(self, func, *args, **kwargs):
1054
+ """
1055
+ Run a synchronous function in an executor to avoid blocking.
1056
+
1057
+ WHY: AgentManager has synchronous methods but AgentLifecycleManager is async.
1058
+ This allows us to call sync methods without blocking the event loop.
1059
+
1060
+ PERFORMANCE: Uses the default executor which manages a thread pool efficiently.
1061
+ """
1062
+ loop = asyncio.get_event_loop()
1063
+ return await loop.run_in_executor(None, func, *args, **kwargs)
1064
+
953
1065
  async def restore_agent(self, agent_name: str, backup_path: Optional[str] = None) -> LifecycleOperationResult:
954
1066
  """Restore agent from backup."""
955
1067
  start_time = time.time()
@@ -28,9 +28,8 @@ import os
28
28
  import shutil
29
29
  import time
30
30
  import uuid
31
- from dataclasses import dataclass, field, asdict
31
+ from dataclasses import asdict
32
32
  from datetime import datetime
33
- from enum import Enum
34
33
  from pathlib import Path
35
34
  from typing import Dict, List, Optional, Any, Callable, Tuple, Set
36
35
 
@@ -40,105 +39,25 @@ from watchdog.events import FileSystemEventHandler, FileSystemEvent
40
39
  from claude_mpm.core.base_service import BaseService
41
40
  from claude_mpm.services.shared_prompt_cache import SharedPromptCache
42
41
  from claude_mpm.services.agent_registry import AgentRegistry
42
+ from claude_mpm.models.modification import (
43
+ ModificationType,
44
+ ModificationTier,
45
+ AgentModification,
46
+ ModificationHistory
47
+ )
43
48
  from claude_mpm.utils.path_operations import path_ops
44
49
  from claude_mpm.utils.config_manager import ConfigurationManager
45
50
 
46
-
47
- # ============================================================================
48
- # Data Models
49
- # ============================================================================
50
-
51
- class ModificationType(Enum):
52
- """Types of agent modifications."""
53
- CREATE = "create"
54
- MODIFY = "modify"
55
- DELETE = "delete"
56
- MOVE = "move"
57
- RESTORE = "restore"
58
-
59
-
60
- class ModificationTier(Enum):
61
- """Agent hierarchy tiers for modification tracking."""
62
- PROJECT = "project"
63
- USER = "user"
64
- SYSTEM = "system"
65
-
66
-
67
- @dataclass
68
- class AgentModification:
69
- """Agent modification record with comprehensive metadata."""
70
-
71
- modification_id: str
72
- agent_name: str
73
- modification_type: ModificationType
74
- tier: ModificationTier
75
- file_path: str
76
- timestamp: float
77
- user_id: Optional[str] = None
78
- modification_details: Dict[str, Any] = field(default_factory=dict)
79
- file_hash_before: Optional[str] = None
80
- file_hash_after: Optional[str] = None
81
- file_size_before: Optional[int] = None
82
- file_size_after: Optional[int] = None
83
- backup_path: Optional[str] = None
84
- validation_status: str = "pending"
85
- validation_errors: List[str] = field(default_factory=list)
86
- related_modifications: List[str] = field(default_factory=list)
87
- metadata: Dict[str, Any] = field(default_factory=dict)
88
-
89
- @property
90
- def modification_datetime(self) -> datetime:
91
- """Get modification timestamp as datetime."""
92
- return datetime.fromtimestamp(self.timestamp)
93
-
94
- @property
95
- def age_seconds(self) -> float:
96
- """Get age of modification in seconds."""
97
- return time.time() - self.timestamp
98
-
99
- def to_dict(self) -> Dict[str, Any]:
100
- """Convert to dictionary for serialization."""
101
- data = asdict(self)
102
- data['modification_type'] = self.modification_type.value
103
- data['tier'] = self.tier.value
104
- return data
105
-
106
- @classmethod
107
- def from_dict(cls, data: Dict[str, Any]) -> 'AgentModification':
108
- """Create from dictionary."""
109
- data['modification_type'] = ModificationType(data['modification_type'])
110
- data['tier'] = ModificationTier(data['tier'])
111
- return cls(**data)
112
-
113
-
114
- @dataclass
115
- class ModificationHistory:
116
- """Complete modification history for an agent."""
117
-
118
- agent_name: str
119
- modifications: List[AgentModification] = field(default_factory=list)
120
- current_version: Optional[str] = None
121
- total_modifications: int = 0
122
- first_seen: Optional[float] = None
123
- last_modified: Optional[float] = None
124
-
125
- def add_modification(self, modification: AgentModification) -> None:
126
- """Add a modification to history."""
127
- self.modifications.append(modification)
128
- self.total_modifications += 1
129
- self.last_modified = modification.timestamp
130
-
131
- if self.first_seen is None:
132
- self.first_seen = modification.timestamp
133
-
134
- def get_recent_modifications(self, hours: int = 24) -> List[AgentModification]:
135
- """Get modifications within specified hours."""
136
- cutoff = time.time() - (hours * 3600)
137
- return [mod for mod in self.modifications if mod.timestamp >= cutoff]
138
-
139
- def get_modifications_by_type(self, mod_type: ModificationType) -> List[AgentModification]:
140
- """Get modifications by type."""
141
- return [mod for mod in self.modifications if mod.modification_type == mod_type]
51
+ # Backward compatibility exports
52
+ # These allow existing code to import models from this module
53
+ __all__ = [
54
+ 'ModificationType',
55
+ 'ModificationTier',
56
+ 'AgentModification',
57
+ 'ModificationHistory',
58
+ 'AgentModificationTracker',
59
+ 'AgentFileSystemHandler'
60
+ ]
142
61
 
143
62
 
144
63
  # ============================================================================
@@ -341,6 +260,14 @@ class AgentModificationTracker(BaseService):
341
260
  backup_path = await self._create_backup(file_path, modification_id)
342
261
 
343
262
  # Create modification record
263
+ # Only include valid AgentModification fields from file_metadata
264
+ valid_metadata_fields = {'file_hash_after', 'file_size_after'}
265
+ filtered_metadata = {k: v for k, v in file_metadata.items() if k in valid_metadata_fields}
266
+
267
+ # Add other metadata to the metadata field
268
+ extra_metadata = {k: v for k, v in file_metadata.items() if k not in valid_metadata_fields}
269
+ extra_metadata.update(kwargs)
270
+
344
271
  modification = AgentModification(
345
272
  modification_id=modification_id,
346
273
  agent_name=agent_name,
@@ -349,8 +276,8 @@ class AgentModificationTracker(BaseService):
349
276
  file_path=file_path,
350
277
  timestamp=time.time(),
351
278
  backup_path=backup_path,
352
- **file_metadata,
353
- **kwargs
279
+ metadata=extra_metadata,
280
+ **filtered_metadata
354
281
  )
355
282
 
356
283
  # Validate modification if enabled
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Agent Persistence Service (Stub)
4
+ ================================
5
+
6
+ WHY: This is a stub implementation to support the AgentLifecycleManager integration.
7
+ The actual persistence is now handled by AgentManager, but we maintain this interface
8
+ for backward compatibility.
9
+
10
+ DESIGN DECISION: Creating a minimal stub because:
11
+ - AgentManager handles the actual file persistence
12
+ - This maintains the existing API contract
13
+ - Allows for future extension if needed
14
+ """
15
+
16
+ from typing import Optional, Any
17
+ import time
18
+
19
+ from claude_mpm.models.persistence import (
20
+ PersistenceStrategy,
21
+ PersistenceOperation,
22
+ PersistenceRecord
23
+ )
24
+
25
+ # Backward compatibility exports
26
+ __all__ = [
27
+ 'PersistenceStrategy',
28
+ 'PersistenceOperation',
29
+ 'PersistenceRecord',
30
+ 'AgentPersistenceService'
31
+ ]
32
+
33
+
34
+ class AgentPersistenceService:
35
+ """
36
+ Stub implementation for agent persistence service.
37
+
38
+ WHY: Maintains compatibility with AgentLifecycleManager while
39
+ actual persistence is delegated to AgentManager.
40
+ """
41
+
42
+ def __init__(self):
43
+ """Initialize the persistence service."""
44
+ pass
45
+
46
+ async def start(self) -> None:
47
+ """Start the persistence service."""
48
+ # No-op for stub
49
+ pass
50
+
51
+ async def stop(self) -> None:
52
+ """Stop the persistence service."""
53
+ # No-op for stub
54
+ pass
55
+
56
+ async def persist_agent(self, agent_name: str, agent_content: str,
57
+ source_tier: Any, target_tier: Optional[Any] = None,
58
+ strategy: Optional[PersistenceStrategy] = None) -> PersistenceRecord:
59
+ """
60
+ Create a persistence record (actual persistence handled by AgentManager).
61
+
62
+ WHY: This method exists for API compatibility but doesn't perform
63
+ actual file operations since AgentManager handles that.
64
+ """
65
+ return PersistenceRecord(
66
+ operation_id=f"persist_{agent_name}_{time.time()}",
67
+ operation_type=PersistenceOperation.UPDATE,
68
+ agent_name=agent_name,
69
+ source_tier=source_tier,
70
+ target_tier=target_tier or source_tier,
71
+ strategy=strategy or PersistenceStrategy.USER_OVERRIDE,
72
+ success=True,
73
+ timestamp=time.time()
74
+ )