claude-mpm 3.4.27__py3-none-any.whl → 3.5.0__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 (123) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +182 -299
  3. claude_mpm/agents/agent_loader.py +283 -57
  4. claude_mpm/agents/agent_loader_integration.py +6 -9
  5. claude_mpm/agents/base_agent.json +2 -1
  6. claude_mpm/agents/base_agent_loader.py +1 -1
  7. claude_mpm/cli/__init__.py +5 -7
  8. claude_mpm/cli/commands/__init__.py +0 -2
  9. claude_mpm/cli/commands/agents.py +1 -1
  10. claude_mpm/cli/commands/memory.py +1 -1
  11. claude_mpm/cli/commands/run.py +12 -0
  12. claude_mpm/cli/parser.py +0 -13
  13. claude_mpm/cli/utils.py +1 -1
  14. claude_mpm/config/__init__.py +44 -2
  15. claude_mpm/config/agent_config.py +348 -0
  16. claude_mpm/config/paths.py +322 -0
  17. claude_mpm/constants.py +0 -1
  18. claude_mpm/core/__init__.py +2 -5
  19. claude_mpm/core/agent_registry.py +63 -17
  20. claude_mpm/core/claude_runner.py +354 -43
  21. claude_mpm/core/config.py +7 -1
  22. claude_mpm/core/config_aliases.py +4 -3
  23. claude_mpm/core/config_paths.py +151 -0
  24. claude_mpm/core/factories.py +4 -50
  25. claude_mpm/core/logger.py +11 -13
  26. claude_mpm/core/service_registry.py +2 -2
  27. claude_mpm/dashboard/static/js/components/agent-inference.js +101 -25
  28. claude_mpm/dashboard/static/js/components/event-processor.js +3 -2
  29. claude_mpm/hooks/claude_hooks/hook_handler.py +343 -83
  30. claude_mpm/hooks/memory_integration_hook.py +1 -1
  31. claude_mpm/init.py +37 -6
  32. claude_mpm/scripts/socketio_daemon.py +6 -2
  33. claude_mpm/services/__init__.py +71 -3
  34. claude_mpm/services/agents/__init__.py +85 -0
  35. claude_mpm/services/agents/deployment/__init__.py +21 -0
  36. claude_mpm/services/{agent_deployment.py → agents/deployment/agent_deployment.py} +192 -41
  37. claude_mpm/services/{agent_lifecycle_manager.py → agents/deployment/agent_lifecycle_manager.py} +11 -10
  38. claude_mpm/services/agents/loading/__init__.py +11 -0
  39. claude_mpm/services/{agent_profile_loader.py → agents/loading/agent_profile_loader.py} +9 -8
  40. claude_mpm/services/{base_agent_manager.py → agents/loading/base_agent_manager.py} +2 -2
  41. claude_mpm/services/{framework_agent_loader.py → agents/loading/framework_agent_loader.py} +116 -40
  42. claude_mpm/services/agents/management/__init__.py +9 -0
  43. claude_mpm/services/{agent_management_service.py → agents/management/agent_management_service.py} +6 -5
  44. claude_mpm/services/agents/memory/__init__.py +21 -0
  45. claude_mpm/services/{agent_memory_manager.py → agents/memory/agent_memory_manager.py} +3 -3
  46. claude_mpm/services/agents/registry/__init__.py +29 -0
  47. claude_mpm/services/{agent_registry.py → agents/registry/agent_registry.py} +101 -16
  48. claude_mpm/services/{deployed_agent_discovery.py → agents/registry/deployed_agent_discovery.py} +12 -2
  49. claude_mpm/services/{agent_modification_tracker.py → agents/registry/modification_tracker.py} +6 -5
  50. claude_mpm/services/async_session_logger.py +584 -0
  51. claude_mpm/services/claude_session_logger.py +299 -0
  52. claude_mpm/services/framework_claude_md_generator/content_assembler.py +2 -2
  53. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +17 -17
  54. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +3 -3
  55. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +1 -1
  56. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +1 -1
  57. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +19 -24
  58. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +1 -1
  59. claude_mpm/services/framework_claude_md_generator.py +4 -2
  60. claude_mpm/services/memory/__init__.py +17 -0
  61. claude_mpm/services/{memory_builder.py → memory/builder.py} +3 -3
  62. claude_mpm/services/memory/cache/__init__.py +14 -0
  63. claude_mpm/services/{shared_prompt_cache.py → memory/cache/shared_prompt_cache.py} +1 -1
  64. claude_mpm/services/memory/cache/simple_cache.py +317 -0
  65. claude_mpm/services/{memory_optimizer.py → memory/optimizer.py} +1 -1
  66. claude_mpm/services/{memory_router.py → memory/router.py} +1 -1
  67. claude_mpm/services/optimized_hook_service.py +542 -0
  68. claude_mpm/services/project_registry.py +14 -8
  69. claude_mpm/services/response_tracker.py +237 -0
  70. claude_mpm/services/ticketing_service_original.py +4 -2
  71. claude_mpm/services/version_control/branch_strategy.py +3 -1
  72. claude_mpm/utils/paths.py +12 -10
  73. claude_mpm/utils/session_logging.py +114 -0
  74. claude_mpm/validation/agent_validator.py +2 -1
  75. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/METADATA +26 -20
  76. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/RECORD +83 -106
  77. claude_mpm/cli/commands/ui.py +0 -57
  78. claude_mpm/core/simple_runner.py +0 -1046
  79. claude_mpm/hooks/builtin/__init__.py +0 -1
  80. claude_mpm/hooks/builtin/logging_hook_example.py +0 -165
  81. claude_mpm/hooks/builtin/memory_hooks_example.py +0 -67
  82. claude_mpm/hooks/builtin/mpm_command_hook.py +0 -125
  83. claude_mpm/hooks/builtin/post_delegation_hook_example.py +0 -124
  84. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +0 -125
  85. claude_mpm/hooks/builtin/submit_hook_example.py +0 -100
  86. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +0 -237
  87. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +0 -240
  88. claude_mpm/hooks/builtin/workflow_start_hook.py +0 -181
  89. claude_mpm/orchestration/__init__.py +0 -6
  90. claude_mpm/orchestration/archive/direct_orchestrator.py +0 -195
  91. claude_mpm/orchestration/archive/factory.py +0 -215
  92. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +0 -188
  93. claude_mpm/orchestration/archive/hook_integration_example.py +0 -178
  94. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +0 -826
  95. claude_mpm/orchestration/archive/orchestrator.py +0 -501
  96. claude_mpm/orchestration/archive/pexpect_orchestrator.py +0 -252
  97. claude_mpm/orchestration/archive/pty_orchestrator.py +0 -270
  98. claude_mpm/orchestration/archive/simple_orchestrator.py +0 -82
  99. claude_mpm/orchestration/archive/subprocess_orchestrator.py +0 -801
  100. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +0 -278
  101. claude_mpm/orchestration/archive/wrapper_orchestrator.py +0 -187
  102. claude_mpm/schemas/workflow_validator.py +0 -411
  103. claude_mpm/services/parent_directory_manager/__init__.py +0 -577
  104. claude_mpm/services/parent_directory_manager/backup_manager.py +0 -258
  105. claude_mpm/services/parent_directory_manager/config_manager.py +0 -210
  106. claude_mpm/services/parent_directory_manager/deduplication_manager.py +0 -279
  107. claude_mpm/services/parent_directory_manager/framework_protector.py +0 -143
  108. claude_mpm/services/parent_directory_manager/operations.py +0 -186
  109. claude_mpm/services/parent_directory_manager/state_manager.py +0 -624
  110. claude_mpm/services/parent_directory_manager/template_deployer.py +0 -579
  111. claude_mpm/services/parent_directory_manager/validation_manager.py +0 -378
  112. claude_mpm/services/parent_directory_manager/version_control_helper.py +0 -339
  113. claude_mpm/services/parent_directory_manager/version_manager.py +0 -222
  114. claude_mpm/ui/__init__.py +0 -1
  115. claude_mpm/ui/rich_terminal_ui.py +0 -295
  116. claude_mpm/ui/terminal_ui.py +0 -328
  117. /claude_mpm/services/{agent_versioning.py → agents/deployment/agent_versioning.py} +0 -0
  118. /claude_mpm/services/{agent_capabilities_generator.py → agents/management/agent_capabilities_generator.py} +0 -0
  119. /claude_mpm/services/{agent_persistence_service.py → agents/memory/agent_persistence_service.py} +0 -0
  120. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/WHEEL +0 -0
  121. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/entry_points.txt +0 -0
  122. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/licenses/LICENSE +0 -0
  123. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/top_level.txt +0 -0
claude_mpm/init.py CHANGED
@@ -71,18 +71,23 @@ class ProjectInitializer:
71
71
  - agents/
72
72
  - project-specific/
73
73
  - config/
74
+ - responses/
74
75
  - logs/
75
76
  """
76
77
  try:
77
- # Find project root
78
+ # Find project root - always define project_root for consistent messaging
78
79
  if project_path:
80
+ project_root = project_path
79
81
  self.project_dir = project_path / ".claude-mpm"
80
82
  else:
81
- project_root = self._find_project_root()
82
- if not project_root:
83
- project_root = Path.cwd()
83
+ # Always use current working directory for project directories
84
+ # This ensures .claude-mpm is created where the user launches the tool
85
+ project_root = Path.cwd()
84
86
  self.project_dir = project_root / ".claude-mpm"
85
87
 
88
+ # Check if directory already exists
89
+ directory_existed = self.project_dir.exists()
90
+
86
91
  # Create project directory
87
92
  self.project_dir.mkdir(exist_ok=True)
88
93
 
@@ -90,6 +95,7 @@ class ProjectInitializer:
90
95
  directories = [
91
96
  self.project_dir / "agents" / "project-specific",
92
97
  self.project_dir / "config",
98
+ self.project_dir / "responses",
93
99
  self.project_dir / "logs",
94
100
  ]
95
101
 
@@ -106,11 +112,21 @@ class ProjectInitializer:
106
112
  if not gitignore.exists():
107
113
  gitignore.write_text("logs/\n*.log\n*.pyc\n__pycache__/\n")
108
114
 
115
+ # Log successful creation with details
109
116
  self.logger.info(f"Initialized project directory at {self.project_dir}")
117
+ self.logger.debug(f"Created directories: agents, config, responses, logs")
118
+
119
+ # Print appropriate message to console for visibility during startup
120
+ if directory_existed:
121
+ print(f"✓ Found existing .claude-mpm/ directory in {project_root}")
122
+ else:
123
+ print(f"✓ Initialized .claude-mpm/ in {project_root}")
124
+
110
125
  return True
111
126
 
112
127
  except Exception as e:
113
128
  self.logger.error(f"Failed to initialize project directory: {e}")
129
+ print(f"✗ Failed to create .claude-mpm/ directory: {e}")
114
130
  return False
115
131
 
116
132
  def _find_project_root(self) -> Optional[Path]:
@@ -198,7 +214,6 @@ class ProjectInitializer:
198
214
  'ai_trackdown_pytools',
199
215
  'yaml',
200
216
  'dotenv',
201
- 'rich',
202
217
  'click',
203
218
  'pexpect',
204
219
  'psutil',
@@ -218,9 +233,25 @@ class ProjectInitializer:
218
233
  return dependencies
219
234
 
220
235
  def ensure_initialized(self) -> bool:
221
- """Ensure both user and project directories are initialized."""
236
+ """Ensure both user and project directories are initialized.
237
+
238
+ Shows clear information about where directories are being created.
239
+ """
240
+ # Show working directory info at startup
241
+ cwd = Path.cwd()
242
+ framework_path = Path(__file__).parent.parent.parent
243
+
244
+ # Log startup context
245
+ self.logger.info(f"Working directory: {cwd}")
246
+ self.logger.info(f"Framework path: {framework_path}")
247
+
248
+ # Initialize user directory (in home)
222
249
  user_ok = self.initialize_user_directory()
250
+
251
+ # Initialize project directory (in current working directory)
252
+ self.logger.info(f"Checking for .claude-mpm/ in {cwd}")
223
253
  project_ok = self.initialize_project_directory()
254
+
224
255
  return user_ok and project_ok
225
256
 
226
257
 
@@ -71,8 +71,12 @@ except ImportError:
71
71
  print(" 3. Check that PYTHONPATH includes the package location")
72
72
  sys.exit(1)
73
73
 
74
- PID_FILE = Path.home() / ".claude-mpm" / "socketio-server.pid"
75
- LOG_FILE = Path.home() / ".claude-mpm" / "socketio-server.log"
74
+ # Use deployment root for daemon files to keep everything centralized
75
+ from claude_mpm.deployment_paths import get_project_root
76
+
77
+ deployment_root = get_project_root()
78
+ PID_FILE = deployment_root / ".claude-mpm" / "socketio-server.pid"
79
+ LOG_FILE = deployment_root / ".claude-mpm" / "socketio-server.log"
76
80
 
77
81
  def ensure_dirs():
78
82
  """Ensure required directories exist."""
@@ -7,14 +7,48 @@ def __getattr__(name):
7
7
  from .ticket_manager import TicketManager
8
8
  return TicketManager
9
9
  elif name == "AgentDeploymentService":
10
- from .agent_deployment import AgentDeploymentService
10
+ from .agents.deployment import AgentDeploymentService
11
11
  return AgentDeploymentService
12
12
  elif name == "AgentMemoryManager":
13
- from .agent_memory_manager import AgentMemoryManager
13
+ from .agents.memory import AgentMemoryManager
14
14
  return AgentMemoryManager
15
15
  elif name == "get_memory_manager":
16
- from .agent_memory_manager import get_memory_manager
16
+ from .agents.memory import get_memory_manager
17
17
  return get_memory_manager
18
+ # Add backward compatibility for other agent services
19
+ elif name == "AgentRegistry":
20
+ from .agents.registry import AgentRegistry
21
+ return AgentRegistry
22
+ elif name == "AgentLifecycleManager":
23
+ from .agents.deployment import AgentLifecycleManager
24
+ return AgentLifecycleManager
25
+ elif name == "AgentManager":
26
+ from .agents.management import AgentManager
27
+ return AgentManager
28
+ elif name == "AgentCapabilitiesGenerator":
29
+ from .agents.management import AgentCapabilitiesGenerator
30
+ return AgentCapabilitiesGenerator
31
+ elif name == "AgentModificationTracker":
32
+ from .agents.registry import AgentModificationTracker
33
+ return AgentModificationTracker
34
+ elif name == "AgentPersistenceService":
35
+ from .agents.memory import AgentPersistenceService
36
+ return AgentPersistenceService
37
+ elif name == "AgentProfileLoader":
38
+ from .agents.loading import AgentProfileLoader
39
+ return AgentProfileLoader
40
+ elif name == "AgentVersionManager":
41
+ from .agents.deployment import AgentVersionManager
42
+ return AgentVersionManager
43
+ elif name == "BaseAgentManager":
44
+ from .agents.loading import BaseAgentManager
45
+ return BaseAgentManager
46
+ elif name == "DeployedAgentDiscovery":
47
+ from .agents.registry import DeployedAgentDiscovery
48
+ return DeployedAgentDiscovery
49
+ elif name == "FrameworkAgentLoader":
50
+ from .agents.loading import FrameworkAgentLoader
51
+ return FrameworkAgentLoader
18
52
  elif name == "HookService":
19
53
  from .hook_service import HookService
20
54
  return HookService
@@ -36,6 +70,22 @@ def __getattr__(name):
36
70
  elif name == "StandaloneSocketIOServer":
37
71
  from .standalone_socketio_server import StandaloneSocketIOServer
38
72
  return StandaloneSocketIOServer
73
+ # Backward compatibility for memory services
74
+ elif name == "MemoryBuilder":
75
+ from .memory.builder import MemoryBuilder
76
+ return MemoryBuilder
77
+ elif name == "MemoryRouter":
78
+ from .memory.router import MemoryRouter
79
+ return MemoryRouter
80
+ elif name == "MemoryOptimizer":
81
+ from .memory.optimizer import MemoryOptimizer
82
+ return MemoryOptimizer
83
+ elif name == "SimpleCacheService":
84
+ from .memory.cache.simple_cache import SimpleCacheService
85
+ return SimpleCacheService
86
+ elif name == "SharedPromptCache":
87
+ from .memory.cache.shared_prompt_cache import SharedPromptCache
88
+ return SharedPromptCache
39
89
  raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
40
90
 
41
91
  __all__ = [
@@ -48,4 +98,22 @@ __all__ = [
48
98
  "AdvancedHealthMonitor",
49
99
  "RecoveryManager",
50
100
  "StandaloneSocketIOServer",
101
+ # Additional agent services for backward compatibility
102
+ "AgentRegistry",
103
+ "AgentLifecycleManager",
104
+ "AgentManager",
105
+ "AgentCapabilitiesGenerator",
106
+ "AgentModificationTracker",
107
+ "AgentPersistenceService",
108
+ "AgentProfileLoader",
109
+ "AgentVersionManager",
110
+ "BaseAgentManager",
111
+ "DeployedAgentDiscovery",
112
+ "FrameworkAgentLoader",
113
+ # Memory services (backward compatibility)
114
+ "MemoryBuilder",
115
+ "MemoryRouter",
116
+ "MemoryOptimizer",
117
+ "SimpleCacheService",
118
+ "SharedPromptCache",
51
119
  ]
@@ -0,0 +1,85 @@
1
+ """Agent services module - hierarchical organization of agent-related services."""
2
+
3
+ # Registry exports
4
+ from .registry.agent_registry import (
5
+ AgentRegistry,
6
+ AgentMetadata,
7
+ AgentTier,
8
+ AgentType,
9
+ )
10
+ from .registry.deployed_agent_discovery import DeployedAgentDiscovery
11
+ from .registry.modification_tracker import (
12
+ AgentModificationTracker,
13
+ ModificationType,
14
+ ModificationTier,
15
+ AgentModification,
16
+ ModificationHistory,
17
+ )
18
+
19
+ # Loading exports
20
+ from .loading.framework_agent_loader import FrameworkAgentLoader
21
+ from .loading.agent_profile_loader import AgentProfileLoader
22
+ from .loading.base_agent_manager import BaseAgentManager
23
+
24
+ # Deployment exports
25
+ from .deployment.agent_deployment import AgentDeploymentService
26
+ from .deployment.agent_lifecycle_manager import (
27
+ AgentLifecycleManager,
28
+ LifecycleState,
29
+ LifecycleOperation,
30
+ AgentLifecycleRecord,
31
+ LifecycleOperationResult,
32
+ )
33
+ from .deployment.agent_versioning import AgentVersionManager
34
+
35
+ # Memory exports
36
+ from .memory.agent_memory_manager import (
37
+ AgentMemoryManager,
38
+ get_memory_manager,
39
+ )
40
+ from .memory.agent_persistence_service import (
41
+ AgentPersistenceService,
42
+ PersistenceStrategy,
43
+ PersistenceOperation,
44
+ PersistenceRecord,
45
+ )
46
+
47
+ # Management exports
48
+ from .management.agent_management_service import AgentManager
49
+ from .management.agent_capabilities_generator import AgentCapabilitiesGenerator
50
+
51
+ __all__ = [
52
+ # Registry
53
+ "AgentRegistry",
54
+ "AgentMetadata",
55
+ "AgentTier",
56
+ "AgentType",
57
+ "DeployedAgentDiscovery",
58
+ "AgentModificationTracker",
59
+ "ModificationType",
60
+ "ModificationTier",
61
+ "AgentModification",
62
+ "ModificationHistory",
63
+ # Loading
64
+ "FrameworkAgentLoader",
65
+ "AgentProfileLoader",
66
+ "BaseAgentManager",
67
+ # Deployment
68
+ "AgentDeploymentService",
69
+ "AgentLifecycleManager",
70
+ "LifecycleState",
71
+ "LifecycleOperation",
72
+ "AgentLifecycleRecord",
73
+ "LifecycleOperationResult",
74
+ "AgentVersionManager",
75
+ # Memory
76
+ "AgentMemoryManager",
77
+ "get_memory_manager",
78
+ "AgentPersistenceService",
79
+ "PersistenceStrategy",
80
+ "PersistenceOperation",
81
+ "PersistenceRecord",
82
+ # Management
83
+ "AgentManager",
84
+ "AgentCapabilitiesGenerator",
85
+ ]
@@ -0,0 +1,21 @@
1
+ """Agent deployment and lifecycle management services."""
2
+
3
+ from .agent_deployment import AgentDeploymentService
4
+ from .agent_lifecycle_manager import (
5
+ AgentLifecycleManager,
6
+ LifecycleState,
7
+ LifecycleOperation,
8
+ AgentLifecycleRecord,
9
+ LifecycleOperationResult,
10
+ )
11
+ from .agent_versioning import AgentVersionManager
12
+
13
+ __all__ = [
14
+ "AgentDeploymentService",
15
+ "AgentLifecycleManager",
16
+ "LifecycleState",
17
+ "LifecycleOperation",
18
+ "AgentLifecycleRecord",
19
+ "LifecycleOperationResult",
20
+ "AgentVersionManager",
21
+ ]
@@ -36,6 +36,7 @@ from typing import Optional, List, Dict, Any
36
36
 
37
37
  from claude_mpm.core.logger import get_logger
38
38
  from claude_mpm.constants import EnvironmentVars, Paths, AgentMetadata
39
+ from claude_mpm.config.paths import paths
39
40
 
40
41
 
41
42
  class AgentDeploymentService:
@@ -99,29 +100,32 @@ class AgentDeploymentService:
99
100
  'deployment_errors': {} # Track error types and frequencies
100
101
  }
101
102
 
102
- # Find templates directory
103
- module_path = Path(__file__).parent.parent
103
+ # Find templates directory using centralized path management
104
104
  if templates_dir:
105
105
  self.templates_dir = Path(templates_dir)
106
106
  else:
107
- # Default to src/claude_mpm/agents/templates/
108
- self.templates_dir = module_path / "agents" / "templates"
107
+ # Use centralized paths instead of fragile parent calculations
108
+ self.templates_dir = paths.agents_dir / "templates"
109
109
 
110
110
  # Find base agent file
111
111
  if base_agent_path:
112
112
  self.base_agent_path = Path(base_agent_path)
113
113
  else:
114
- # Default to src/claude_mpm/agents/base_agent.json
115
- self.base_agent_path = module_path / "agents" / "base_agent.json"
114
+ # Use centralized paths for consistency
115
+ self.base_agent_path = paths.agents_dir / "base_agent.json"
116
116
 
117
117
  self.logger.info(f"Templates directory: {self.templates_dir}")
118
118
  self.logger.info(f"Base agent path: {self.base_agent_path}")
119
119
 
120
- def deploy_agents(self, target_dir: Optional[Path] = None, force_rebuild: bool = False) -> Dict[str, Any]:
120
+ def deploy_agents(self, target_dir: Optional[Path] = None, force_rebuild: bool = False, deployment_mode: str = "update") -> Dict[str, Any]:
121
121
  """
122
122
  Build and deploy agents by combining base_agent.md with templates.
123
123
  Also deploys system instructions for PM framework.
124
124
 
125
+ DEPLOYMENT MODES:
126
+ - "update": Normal update mode - skip agents with matching versions (default)
127
+ - "project": Project deployment mode - always deploy all agents regardless of version
128
+
125
129
  METRICS COLLECTED:
126
130
  - Deployment start/end timestamps
127
131
  - Individual agent deployment durations
@@ -161,6 +165,7 @@ class AgentDeploymentService:
161
165
  Args:
162
166
  target_dir: Target directory for agents (default: .claude/agents/)
163
167
  force_rebuild: Force rebuild even if agents exist (useful for troubleshooting)
168
+ deployment_mode: "update" for version-aware updates, "project" for always deploy
164
169
 
165
170
  Returns:
166
171
  Dictionary with deployment results:
@@ -176,11 +181,28 @@ class AgentDeploymentService:
176
181
  deployment_start_time = time.time()
177
182
 
178
183
  if not target_dir:
179
- target_dir = Path(Paths.CLAUDE_AGENTS_DIR.value).expanduser()
184
+ # Default to user's home .claude/agents directory
185
+ agents_dir = Path(Paths.CLAUDE_AGENTS_DIR.value).expanduser()
186
+ else:
187
+ # If target_dir provided, use it directly (caller decides structure)
188
+ # This allows for both passing a project dir or the full agents path
189
+ target_dir = Path(target_dir)
190
+ # Check if this is already an agents directory
191
+ if target_dir.name == "agents":
192
+ # Already an agents directory, use as-is
193
+ agents_dir = target_dir
194
+ elif target_dir.name == ".claude-mpm":
195
+ # .claude-mpm directory, add agents subdirectory
196
+ agents_dir = target_dir / "agents"
197
+ elif target_dir.name == ".claude":
198
+ # .claude directory, add agents subdirectory
199
+ agents_dir = target_dir / "agents"
200
+ else:
201
+ # Assume it's a project directory, add .claude/agents
202
+ agents_dir = target_dir / ".claude" / "agents"
180
203
 
181
- target_dir = Path(target_dir)
182
204
  results = {
183
- "target_dir": str(target_dir),
205
+ "target_dir": str(agents_dir),
184
206
  "deployed": [],
185
207
  "errors": [],
186
208
  "skipped": [],
@@ -200,9 +222,9 @@ class AgentDeploymentService:
200
222
  }
201
223
 
202
224
  try:
203
- # Create target directory if needed
204
- target_dir.mkdir(parents=True, exist_ok=True)
205
- self.logger.info(f"Building and deploying agents to: {target_dir}")
225
+ # Create agents directory if needed
226
+ agents_dir.mkdir(parents=True, exist_ok=True)
227
+ self.logger.info(f"Building and deploying agents to: {agents_dir}")
206
228
 
207
229
  # Note: System instructions are now loaded directly by SimpleClaudeRunner
208
230
 
@@ -214,7 +236,7 @@ class AgentDeploymentService:
214
236
  return results
215
237
 
216
238
  # Convert any existing YAML files to MD format
217
- conversion_results = self._convert_yaml_to_md(target_dir)
239
+ conversion_results = self._convert_yaml_to_md(agents_dir)
218
240
  results["converted"] = conversion_results.get("converted", [])
219
241
 
220
242
  # Load base agent content
@@ -237,8 +259,14 @@ class AgentDeploymentService:
237
259
 
238
260
  # Get all template files
239
261
  template_files = list(self.templates_dir.glob("*.json"))
240
- # Filter out non-agent files
241
- template_files = [f for f in template_files if f.stem != "__init__" and not f.stem.startswith(".")]
262
+ # Filter out non-agent files - exclude system files and uppercase special files
263
+ excluded_names = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", "README"}
264
+ template_files = [
265
+ f for f in template_files
266
+ if f.stem not in excluded_names
267
+ and not f.stem.startswith(".")
268
+ and not f.stem.endswith(".backup")
269
+ ]
242
270
  results["total"] = len(template_files)
243
271
 
244
272
  for template_file in template_files:
@@ -247,12 +275,22 @@ class AgentDeploymentService:
247
275
  agent_start_time = time.time()
248
276
 
249
277
  agent_name = template_file.stem
250
- target_file = target_dir / f"{agent_name}.md"
278
+ target_file = agents_dir / f"{agent_name}.md"
251
279
 
252
280
  # Check if agent needs update
253
281
  needs_update = force_rebuild
254
282
  is_migration = False
255
- if not needs_update and target_file.exists():
283
+
284
+ # In project deployment mode, always deploy regardless of version
285
+ if deployment_mode == "project":
286
+ if target_file.exists():
287
+ # Check if it's a system agent that we should update
288
+ needs_update = True
289
+ self.logger.debug(f"Project deployment mode: will deploy {agent_name}")
290
+ else:
291
+ needs_update = True
292
+ elif not needs_update and target_file.exists():
293
+ # In update mode, check version compatibility
256
294
  needs_update, reason = self._check_agent_needs_update(
257
295
  target_file, template_file, base_agent_version
258
296
  )
@@ -264,8 +302,8 @@ class AgentDeploymentService:
264
302
  else:
265
303
  self.logger.info(f"Agent {agent_name} needs update: {reason}")
266
304
 
267
- # Skip if exists and doesn't need update
268
- if target_file.exists() and not needs_update:
305
+ # Skip if exists and doesn't need update (only in update mode)
306
+ if target_file.exists() and not needs_update and deployment_mode != "project":
269
307
  results["skipped"].append(agent_name)
270
308
  self.logger.debug(f"Skipped up-to-date agent: {agent_name}")
271
309
  continue
@@ -524,26 +562,48 @@ class AgentDeploymentService:
524
562
  model = (
525
563
  template_data.get('capabilities', {}).get('model') or
526
564
  template_data.get('configuration_fields', {}).get('model') or
527
- "claude-sonnet-4-20250514" # Default fallback
565
+ "sonnet" # Default fallback
528
566
  )
529
567
 
530
- frontmatter = f"""---
531
- name: {agent_name}
532
- description: "{description}"
533
- version: "{version_string}"
534
- author: "{template_data.get('author', 'claude-mpm@anthropic.com')}"
535
- created: "{datetime.now().isoformat()}Z"
536
- updated: "{datetime.now().isoformat()}Z"
537
- tags: {tags}
538
- tools: {tools}
539
- model: "{model}"
540
- metadata:
541
- base_version: "{self._format_version_display(base_version)}"
542
- agent_version: "{self._format_version_display(agent_version)}"
543
- deployment_type: "system"
544
- ---
545
-
546
- """
568
+ # Simplify model name for Claude Code
569
+ model_map = {
570
+ 'claude-4-sonnet-20250514': 'sonnet',
571
+ 'claude-sonnet-4-20250514': 'sonnet',
572
+ 'claude-3-opus-20240229': 'opus',
573
+ 'claude-3-haiku-20240307': 'haiku',
574
+ 'claude-3.5-sonnet': 'sonnet',
575
+ 'claude-3-sonnet': 'sonnet'
576
+ }
577
+ model = model_map.get(model, model.split('-')[-1] if '-' in model else model)
578
+
579
+ # Get response format from template or use base agent default
580
+ response_format = template_data.get('response', {}).get('format', 'structured')
581
+
582
+ # Convert lists to space-separated strings for Claude Code compatibility
583
+ tags_str = ' '.join(tags) if isinstance(tags, list) else tags
584
+ tools_str = ', '.join(tools) if isinstance(tools, list) else tools
585
+
586
+ # Build frontmatter with only the fields Claude Code uses
587
+ frontmatter_lines = [
588
+ "---",
589
+ f"name: {agent_name}",
590
+ f"description: {description}",
591
+ f"version: {version_string}",
592
+ f"base_version: {self._format_version_display(base_version)}",
593
+ f"author: claude-mpm", # Identify as system agent for deployment
594
+ f"tools: {tools_str}",
595
+ f"model: {model}"
596
+ ]
597
+
598
+ # Add optional fields if present
599
+ if template_data.get('color'):
600
+ frontmatter_lines.append(f"color: {template_data['color']}")
601
+
602
+ frontmatter_lines.append("---")
603
+ frontmatter_lines.append("")
604
+ frontmatter_lines.append("")
605
+
606
+ frontmatter = '\n'.join(frontmatter_lines)
547
607
 
548
608
  # Get the main content (instructions)
549
609
  # Check multiple possible locations for instructions
@@ -874,14 +934,18 @@ temperature: {temperature}"""
874
934
  "path": str(agent_file)
875
935
  }
876
936
 
877
- # Extract name and version from YAML frontmatter
937
+ # Extract name, version, and base_version from YAML frontmatter
878
938
  version_str = None
939
+ base_version_str = None
879
940
  for line in lines:
880
941
  if line.startswith("name:"):
881
942
  agent_info["name"] = line.split(":", 1)[1].strip().strip('"\'')
882
943
  elif line.startswith("version:"):
883
944
  version_str = line.split(":", 1)[1].strip().strip('"\'')
884
945
  agent_info["version"] = version_str
946
+ elif line.startswith("base_version:"):
947
+ base_version_str = line.split(":", 1)[1].strip().strip('"\'')
948
+ agent_info["base_version"] = base_version_str
885
949
 
886
950
  # Check if agent needs migration
887
951
  if version_str and self._is_old_version_format(version_str):
@@ -904,6 +968,87 @@ temperature: {temperature}"""
904
968
 
905
969
  return results
906
970
 
971
+ def deploy_agent(self, agent_name: str, target_dir: Path, force_rebuild: bool = False) -> bool:
972
+ """
973
+ Deploy a single agent to the specified directory.
974
+
975
+ Args:
976
+ agent_name: Name of the agent to deploy
977
+ target_dir: Target directory for deployment (Path object)
978
+ force_rebuild: Whether to force rebuild even if version is current
979
+
980
+ Returns:
981
+ True if deployment was successful, False otherwise
982
+
983
+ WHY: Single agent deployment because:
984
+ - Users may want to deploy specific agents only
985
+ - Reduces deployment time for targeted updates
986
+ - Enables selective agent management in projects
987
+
988
+ FIXED: Method now correctly handles all internal calls to:
989
+ - _check_agent_needs_update (with 3 arguments)
990
+ - _build_agent_markdown (with 3 arguments including base_agent_data)
991
+ - Properly loads base_agent_data before building agent content
992
+ """
993
+ try:
994
+ # Find the template file
995
+ template_file = self.templates_dir / f"{agent_name}.json"
996
+ if not template_file.exists():
997
+ self.logger.error(f"Agent template not found: {agent_name}")
998
+ return False
999
+
1000
+ # Ensure target directory exists
1001
+ agents_dir = target_dir / '.claude' / 'agents'
1002
+ agents_dir.mkdir(parents=True, exist_ok=True)
1003
+
1004
+ # Build and deploy the agent
1005
+ target_file = agents_dir / f"{agent_name}.md"
1006
+
1007
+ # Check if update is needed
1008
+ if not force_rebuild and target_file.exists():
1009
+ # Load base agent data for version checking
1010
+ base_agent_data = {}
1011
+ base_agent_version = (0, 0, 0)
1012
+ if self.base_agent_path.exists():
1013
+ try:
1014
+ import json
1015
+ base_agent_data = json.loads(self.base_agent_path.read_text())
1016
+ base_agent_version = self._parse_version(base_agent_data.get('base_version') or base_agent_data.get('version', 0))
1017
+ except Exception as e:
1018
+ self.logger.warning(f"Could not load base agent for version check: {e}")
1019
+
1020
+ needs_update, reason = self._check_agent_needs_update(target_file, template_file, base_agent_version)
1021
+ if not needs_update:
1022
+ self.logger.info(f"Agent {agent_name} is up to date")
1023
+ return True
1024
+ else:
1025
+ self.logger.info(f"Updating agent {agent_name}: {reason}")
1026
+
1027
+ # Load base agent data for building
1028
+ base_agent_data = {}
1029
+ if self.base_agent_path.exists():
1030
+ try:
1031
+ import json
1032
+ base_agent_data = json.loads(self.base_agent_path.read_text())
1033
+ except Exception as e:
1034
+ self.logger.warning(f"Could not load base agent: {e}")
1035
+
1036
+ # Build the agent markdown
1037
+ agent_content = self._build_agent_markdown(agent_name, template_file, base_agent_data)
1038
+ if not agent_content:
1039
+ self.logger.error(f"Failed to build agent content for {agent_name}")
1040
+ return False
1041
+
1042
+ # Write to target file
1043
+ target_file.write_text(agent_content)
1044
+ self.logger.info(f"Successfully deployed agent: {agent_name} to {target_file}")
1045
+
1046
+ return True
1047
+
1048
+ except Exception as e:
1049
+ self.logger.error(f"Failed to deploy agent {agent_name}: {e}")
1050
+ return False
1051
+
907
1052
  def list_available_agents(self) -> List[Dict[str, Any]]:
908
1053
  """
909
1054
  List available agent templates.
@@ -918,8 +1063,14 @@ temperature: {temperature}"""
918
1063
  return agents
919
1064
 
920
1065
  template_files = sorted(self.templates_dir.glob("*.json"))
921
- # Filter out non-agent files
922
- template_files = [f for f in template_files if f.stem != "__init__" and not f.stem.startswith(".")]
1066
+ # Filter out non-agent files - exclude system files and uppercase special files
1067
+ excluded_names = {"__init__", "MEMORIES", "TODOWRITE", "INSTRUCTIONS", "README"}
1068
+ template_files = [
1069
+ f for f in template_files
1070
+ if f.stem not in excluded_names
1071
+ and not f.stem.startswith(".")
1072
+ and not f.stem.endswith(".backup")
1073
+ ]
923
1074
 
924
1075
  for template_file in template_files:
925
1076
  try: